cloving 1.3.0 → 1.4.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 (396) hide show
  1. package/README.md +235 -206
  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-queries.d.ts +2 -1
  32. package/dist/src/broker/actor/authority-queries.js +14 -5
  33. package/dist/src/broker/actor/authority-receipts.js +1 -1
  34. package/dist/src/broker/actor/authority-settings.js +2 -2
  35. package/dist/src/broker/actor/authority-upgrades.d.ts +19 -2
  36. package/dist/src/broker/actor/authority-upgrades.js +7 -2
  37. package/dist/src/broker/actor/delivery-intents.js +2 -0
  38. package/dist/src/broker/actor/effect-delivery.d.ts +1 -1
  39. package/dist/src/broker/actor/effect-delivery.js +15 -2
  40. package/dist/src/broker/actor/effect-plan.js +11 -2
  41. package/dist/src/broker/actor/effect-queries.d.ts +4 -0
  42. package/dist/src/broker/actor/effect-queries.js +9 -1
  43. package/dist/src/broker/actor/effect-turn.js +2 -0
  44. package/dist/src/broker/actor/stop-plan.js +9 -3
  45. package/dist/src/broker/actor/stop-turn.d.ts +2 -0
  46. package/dist/src/broker/actor/stop-turn.js +24 -11
  47. package/dist/src/broker/actor/turn-draft.d.ts +1 -0
  48. package/dist/src/broker/actor/turn-draft.js +4 -0
  49. package/dist/src/broker/actor/turn-state.js +5 -1
  50. package/dist/src/broker/authority-admission.js +60 -0
  51. package/dist/src/broker/authority-restore.d.ts +2 -0
  52. package/dist/src/broker/authority-restore.js +35 -1
  53. package/dist/src/broker/command-wait-restore.d.ts +31 -0
  54. package/dist/src/broker/command-wait-restore.js +86 -0
  55. package/dist/src/broker/decision-restore.d.ts +1 -1
  56. package/dist/src/broker/decision-restore.js +1 -0
  57. package/dist/src/broker/effect-upgrade.d.ts +2 -0
  58. package/dist/src/broker/effect-upgrade.js +8 -2
  59. package/dist/src/cli/arguments.d.ts +13 -0
  60. package/dist/src/cli/arguments.js +42 -3
  61. package/dist/src/cli/chat-stores.d.ts +2 -0
  62. package/dist/src/cli/chat-stores.js +113 -0
  63. package/dist/src/cli/client.d.ts +4 -1
  64. package/dist/src/cli/client.js +9 -3
  65. package/dist/src/cli/command-requests.d.ts +17 -0
  66. package/dist/src/cli/command-requests.js +43 -0
  67. package/dist/src/cli/command-waits.d.ts +9 -0
  68. package/dist/src/cli/command-waits.js +51 -0
  69. package/dist/src/cli/commands.js +14 -41
  70. package/dist/src/cli/config-file.d.ts +2 -2
  71. package/dist/src/cli/config-file.js +4 -2
  72. package/dist/src/cli/doctor-workflows.d.ts +5 -2
  73. package/dist/src/cli/doctor-workflows.js +12 -7
  74. package/dist/src/cli/doctor.js +6 -9
  75. package/dist/src/cli/harness-probes.d.ts +2 -0
  76. package/dist/src/cli/harness-probes.js +1 -1
  77. package/dist/src/cli/harness-processes.d.ts +6 -0
  78. package/dist/src/cli/harness-processes.js +36 -0
  79. package/dist/src/cli/init-command.d.ts +9 -0
  80. package/dist/src/cli/init-command.js +52 -0
  81. package/dist/src/cli/init-prompt-command.d.ts +10 -0
  82. package/dist/src/cli/init-prompt-command.js +145 -0
  83. package/dist/src/cli/init-prompt.d.ts +35 -0
  84. package/dist/src/cli/init-prompt.js +74 -0
  85. package/dist/src/cli/init-selection.d.ts +13 -0
  86. package/dist/src/cli/init-selection.js +75 -0
  87. package/dist/src/cli/main.d.ts +2 -0
  88. package/dist/src/cli/main.js +37 -7
  89. package/dist/src/cli/mcp-server.d.ts +1 -0
  90. package/dist/src/cli/mcp-server.js +156 -0
  91. package/dist/src/cli/mcp-tools.d.ts +15 -0
  92. package/dist/src/cli/mcp-tools.js +80 -0
  93. package/dist/src/cli/render.d.ts +1 -1
  94. package/dist/src/cli/render.js +17 -30
  95. package/dist/src/cli/report-ci-jobs.d.ts +23 -0
  96. package/dist/src/cli/report-ci-jobs.js +103 -0
  97. package/dist/src/cli/report-ci-usage.d.ts +9 -0
  98. package/dist/src/cli/report-ci-usage.js +185 -0
  99. package/dist/src/cli/report-ci.d.ts +13 -0
  100. package/dist/src/cli/report-ci.js +74 -0
  101. package/dist/src/cli/report-closures.d.ts +6 -0
  102. package/dist/src/cli/report-closures.js +97 -0
  103. package/dist/src/cli/report-flow.d.ts +7 -0
  104. package/dist/src/cli/report-flow.js +122 -0
  105. package/dist/src/cli/report-github-reader.d.ts +29 -0
  106. package/dist/src/cli/report-github-reader.js +157 -0
  107. package/dist/src/cli/report-github.d.ts +15 -8
  108. package/dist/src/cli/report-github.js +131 -150
  109. package/dist/src/cli/report-operator.d.ts +3 -0
  110. package/dist/src/cli/report-operator.js +40 -0
  111. package/dist/src/cli/report-ready.d.ts +3 -0
  112. package/dist/src/cli/report-ready.js +96 -0
  113. package/dist/src/cli/report-text.d.ts +9 -0
  114. package/dist/src/cli/report-text.js +33 -0
  115. package/dist/src/cli/report.d.ts +1 -0
  116. package/dist/src/cli/report.js +104 -27
  117. package/dist/src/cli/runtime-arguments.d.ts +2 -1
  118. package/dist/src/cli/runtime-arguments.js +7 -2
  119. package/dist/src/cli/schedule-arguments.d.ts +14 -0
  120. package/dist/src/cli/schedule-arguments.js +26 -0
  121. package/dist/src/cli/schedule-render.d.ts +22 -0
  122. package/dist/src/cli/schedule-render.js +73 -0
  123. package/dist/src/cli/terminal-text.d.ts +3 -0
  124. package/dist/src/cli/terminal-text.js +28 -0
  125. package/dist/src/cli/usage-render.d.ts +1 -0
  126. package/dist/src/cli/usage-render.js +28 -1
  127. package/dist/src/cli/usage.d.ts +1 -1
  128. package/dist/src/cli/usage.js +50 -7
  129. package/dist/src/cli/watch-actions.js +2 -2
  130. package/dist/src/cli/watch-board-view.js +19 -2
  131. package/dist/src/cli/watch-board.d.ts +4 -2
  132. package/dist/src/cli/watch-board.js +53 -43
  133. package/dist/src/cli/watch-card-forms.d.ts +1 -1
  134. package/dist/src/cli/watch-card-forms.js +11 -10
  135. package/dist/src/cli/watch-client.d.ts +1 -1
  136. package/dist/src/cli/watch-client.js +2 -11
  137. package/dist/src/cli/watch-header-view.d.ts +0 -5
  138. package/dist/src/cli/watch-header-view.js +8 -20
  139. package/dist/src/cli/watch-model.d.ts +4 -1
  140. package/dist/src/cli/watch-model.js +17 -11
  141. package/dist/src/cli/watch-page.js +12 -14
  142. package/dist/src/cli/watch-phases.d.ts +12 -3
  143. package/dist/src/cli/watch-phases.js +68 -11
  144. package/dist/src/cli/watch-pr-board.js +1 -1
  145. package/dist/src/cli/watch-pr-command.d.ts +1 -0
  146. package/dist/src/cli/watch-pull-request.js +18 -8
  147. package/dist/src/cli/watch-reads.d.ts +2 -0
  148. package/dist/src/cli/watch-reads.js +17 -16
  149. package/dist/src/cli/watch-render.d.ts +14 -0
  150. package/dist/src/cli/watch-run-input.d.ts +0 -1
  151. package/dist/src/cli/watch-run-input.js +0 -13
  152. package/dist/src/cli/watch-server.js +6 -6
  153. package/dist/src/cli/watch-view.d.ts +0 -3
  154. package/dist/src/cli/watch-view.js +13 -13
  155. package/dist/src/cli.js +59 -5
  156. package/dist/src/command-wait.d.ts +4 -0
  157. package/dist/src/command-wait.js +45 -0
  158. package/dist/src/configuration-change.d.ts +7 -0
  159. package/dist/src/configuration-change.js +14 -0
  160. package/dist/src/configuration-schema.d.ts +9 -0
  161. package/dist/src/configuration-schema.js +6 -2
  162. package/dist/src/daemon/attachment-file.d.ts +6 -0
  163. package/dist/src/daemon/attachment-file.js +86 -0
  164. package/dist/src/daemon/attachments.d.ts +14 -0
  165. package/dist/src/daemon/attachments.js +17 -0
  166. package/dist/src/daemon/claude-recovery.d.ts +50 -0
  167. package/dist/src/daemon/claude-recovery.js +204 -0
  168. package/dist/src/daemon/claude-stream.d.ts +27 -0
  169. package/dist/src/daemon/claude-stream.js +63 -0
  170. package/dist/src/daemon/claude-turn.d.ts +11 -25
  171. package/dist/src/daemon/claude-turn.js +64 -81
  172. package/dist/src/daemon/claude-worker.d.ts +2 -1
  173. package/dist/src/daemon/claude-worker.js +124 -122
  174. package/dist/src/daemon/daemon-adapters.d.ts +2 -0
  175. package/dist/src/daemon/daemon-adapters.js +26 -2
  176. package/dist/src/daemon/daemon-loops.d.ts +3 -1
  177. package/dist/src/daemon/daemon-loops.js +3 -1
  178. package/dist/src/daemon/daemon.js +14 -6
  179. package/dist/src/daemon/github-attach.d.ts +2 -0
  180. package/dist/src/daemon/github-attach.js +149 -0
  181. package/dist/src/daemon/github-ci-filters.d.ts +6 -0
  182. package/dist/src/daemon/github-ci-filters.js +138 -0
  183. package/dist/src/daemon/github-ci-workflows.d.ts +10 -0
  184. package/dist/src/daemon/github-ci-workflows.js +148 -0
  185. package/dist/src/daemon/github-ci.js +22 -4
  186. package/dist/src/daemon/github-issue-methods.d.ts +2 -0
  187. package/dist/src/daemon/github-issue-methods.js +37 -2
  188. package/dist/src/daemon/github-merge-call.d.ts +2 -1
  189. package/dist/src/daemon/github-merge-call.js +10 -3
  190. package/dist/src/daemon/github-merge.js +52 -24
  191. package/dist/src/daemon/github-methods.js +12 -2
  192. package/dist/src/daemon/github-operation.d.ts +4 -0
  193. package/dist/src/daemon/github-operation.js +6 -0
  194. package/dist/src/daemon/github-worker.d.ts +4 -0
  195. package/dist/src/daemon/github-worker.js +6 -0
  196. package/dist/src/daemon/issue-queue.d.ts +1 -1
  197. package/dist/src/daemon/issue-queue.js +16 -11
  198. package/dist/src/daemon/notify-events.d.ts +6 -0
  199. package/dist/src/daemon/notify-events.js +84 -0
  200. package/dist/src/daemon/notify-journal.js +1 -1
  201. package/dist/src/daemon/notify-message.d.ts +4 -1
  202. package/dist/src/daemon/notify-message.js +36 -0
  203. package/dist/src/daemon/notify.d.ts +1 -1
  204. package/dist/src/daemon/notify.js +5 -77
  205. package/dist/src/daemon/operation-journal.d.ts +1 -0
  206. package/dist/src/daemon/queue-status.d.ts +9 -3
  207. package/dist/src/daemon/queue-status.js +17 -4
  208. package/dist/src/daemon/resume-index-lock.d.ts +13 -0
  209. package/dist/src/daemon/resume-index-lock.js +160 -0
  210. package/dist/src/daemon/resume-worktree.d.ts +7 -0
  211. package/dist/src/daemon/resume-worktree.js +29 -0
  212. package/dist/src/daemon/retrying-turn.d.ts +2 -12
  213. package/dist/src/daemon/retrying-turn.js +98 -172
  214. package/dist/src/daemon/schedule-control.d.ts +26 -0
  215. package/dist/src/daemon/schedule-control.js +110 -0
  216. package/dist/src/daemon/schedule-journal.d.ts +2 -15
  217. package/dist/src/daemon/schedule-journal.js +67 -4
  218. package/dist/src/daemon/ticket-registration.d.ts +2 -7
  219. package/dist/src/daemon/ticket-registration.js +15 -81
  220. package/dist/src/daemon/turn-attempt-journal.d.ts +38 -0
  221. package/dist/src/daemon/turn-attempt-journal.js +136 -0
  222. package/dist/src/daemon/turn-attempts.d.ts +47 -0
  223. package/dist/src/daemon/turn-attempts.js +166 -0
  224. package/dist/src/daemon/turn-report.d.ts +8 -1
  225. package/dist/src/daemon/turn-report.js +39 -0
  226. package/dist/src/daemon/work-tree.d.ts +9 -0
  227. package/dist/src/daemon/work-tree.js +20 -0
  228. package/dist/src/daemon/worker-profile.d.ts +8 -0
  229. package/dist/src/daemon/worker-profile.js +40 -0
  230. package/dist/src/daemon/workflow-schedules.d.ts +3 -0
  231. package/dist/src/daemon/workflow-schedules.js +98 -45
  232. package/dist/src/daemon/worktree-processes.d.ts +9 -0
  233. package/dist/src/daemon/worktree-processes.js +151 -0
  234. package/dist/src/doctor-report.d.ts +3 -5
  235. package/dist/src/doctor-report.js +6 -13
  236. package/dist/src/harness-start.d.ts +15 -0
  237. package/dist/src/harness-start.js +37 -0
  238. package/dist/src/host/command-wait.d.ts +38 -0
  239. package/dist/src/host/command-wait.js +115 -0
  240. package/dist/src/host/start-delivery.d.ts +5 -7
  241. package/dist/src/host/start-delivery.js +3 -15
  242. package/dist/src/host/step-bridge-host.d.ts +2 -0
  243. package/dist/src/host/step-bridge-host.js +21 -0
  244. package/dist/src/operation-identity.d.ts +1 -0
  245. package/dist/src/operation-identity.js +4 -0
  246. package/dist/src/private-config.schema.json +40 -0
  247. package/dist/src/project-config.schema.json +40 -0
  248. package/dist/src/project-settings.d.ts +2 -2
  249. package/dist/src/project-settings.js +3 -1
  250. package/dist/src/queue-spend.d.ts +4 -0
  251. package/dist/src/queue-spend.js +21 -0
  252. package/dist/src/relay/relay-service.d.ts +3 -0
  253. package/dist/src/relay/relay-service.js +5 -3
  254. package/dist/src/report-ci.d.ts +164 -0
  255. package/dist/src/report-ci.js +32 -0
  256. package/dist/src/report-effects.d.ts +15 -0
  257. package/dist/src/report-effects.js +212 -0
  258. package/dist/src/report-flow-types.d.ts +38 -0
  259. package/dist/src/report-flow-types.js +7 -0
  260. package/dist/src/report-flow.d.ts +43 -0
  261. package/dist/src/report-flow.js +193 -0
  262. package/dist/src/report-operator.d.ts +67 -0
  263. package/dist/src/report-operator.js +25 -0
  264. package/dist/src/report-quality.d.ts +221 -0
  265. package/dist/src/report-quality.js +94 -0
  266. package/dist/src/report-ready.d.ts +23 -0
  267. package/dist/src/report-ready.js +117 -0
  268. package/dist/src/report-statistics.d.ts +2 -1
  269. package/dist/src/report-statistics.js +40 -4
  270. package/dist/src/report.d.ts +588 -47
  271. package/dist/src/report.js +156 -41
  272. package/dist/src/schedule-progress.d.ts +33 -0
  273. package/dist/src/schedule-progress.js +45 -0
  274. package/dist/src/service/active-tickets.js +6 -6
  275. package/dist/src/service/command-waits.d.ts +34 -0
  276. package/dist/src/service/command-waits.js +86 -0
  277. package/dist/src/service/expiry.js +2 -0
  278. package/dist/src/service/gateway.js +3 -1
  279. package/dist/src/service/queue-spend.d.ts +6 -0
  280. package/dist/src/service/queue-spend.js +68 -0
  281. package/dist/src/service/replies.d.ts +8 -1
  282. package/dist/src/service/replies.js +11 -0
  283. package/dist/src/service/run-handlers.js +22 -2
  284. package/dist/src/service/run-metadata.d.ts +15 -1
  285. package/dist/src/service/run-metadata.js +93 -8
  286. package/dist/src/service/service.d.ts +4 -0
  287. package/dist/src/service/service.js +8 -8
  288. package/dist/src/service/submission-handler.js +18 -4
  289. package/dist/src/service/worker-port.js +9 -1
  290. package/dist/src/service/worker-totals.d.ts +2 -0
  291. package/dist/src/service/worker-totals.js +1 -1
  292. package/dist/src/step-bridge.d.ts +20 -0
  293. package/dist/src/steps/command-wait.d.ts +12 -0
  294. package/dist/src/steps/command-wait.js +26 -0
  295. package/dist/src/steps/open-decision-wait.d.ts +2 -0
  296. package/dist/src/steps/{decision-gate.js → open-decision-wait.js} +0 -7
  297. package/dist/src/steps/review-by-person.js +4 -1
  298. package/dist/src/steps/wait-for-command.d.ts +3 -0
  299. package/dist/src/steps/wait-for-command.js +17 -0
  300. package/dist/src/steps/wait-for-decision.js +1 -1
  301. package/dist/src/storage/command-wait-rows.d.ts +30 -0
  302. package/dist/src/storage/command-wait-rows.js +96 -0
  303. package/dist/src/storage/decision-snapshots.d.ts +8 -1
  304. package/dist/src/storage/decision-snapshots.js +15 -1
  305. package/dist/src/storage/domain-changes.d.ts +2 -0
  306. package/dist/src/storage/domain-changes.js +3 -0
  307. package/dist/src/storage/domain-schema.d.ts +13 -1
  308. package/dist/src/storage/domain-schema.js +31 -4
  309. package/dist/src/storage/effect-schema.d.ts +1 -0
  310. package/dist/src/storage/effect-schema.js +5 -0
  311. package/dist/src/testing-requests.d.ts +15 -0
  312. package/dist/src/testing-requests.js +93 -0
  313. package/dist/src/testing.d.ts +4 -10
  314. package/dist/src/testing.js +33 -86
  315. package/dist/src/worker-usage.d.ts +1 -0
  316. package/dist/src/worker-usage.js +3 -0
  317. package/dist/src/workflow-candidate.d.ts +1 -0
  318. package/dist/src/workflow-candidate.js +12 -1
  319. package/dist/src/workflow-contracts.d.ts +1 -0
  320. package/dist/src/workflow-contracts.js +9 -2
  321. package/dist/src/workflow-discovery.d.ts +2 -13
  322. package/dist/src/workflow-discovery.js +1 -13
  323. package/dist/src/workflow-refusals.js +0 -1
  324. package/dist/src/workflows.d.ts +1 -1
  325. package/dist/src/workflows.js +1 -1
  326. package/dist/tools/red-green-command.d.ts +1 -0
  327. package/dist/tools/red-green-command.js +174 -0
  328. package/dist/tools/red-green-process.d.ts +10 -0
  329. package/dist/tools/red-green-process.js +59 -0
  330. package/dist/tools/self-host-processes.d.ts +2 -0
  331. package/dist/tools/self-host-processes.js +32 -13
  332. package/dist/tools/self-host-project.js +13 -78
  333. package/dist/tools/self-host-runtime.js +1 -1
  334. package/dist/tools/self-host.js +42 -10
  335. package/dist/tools/template-command.d.ts +1 -0
  336. package/dist/tools/template-command.js +160 -0
  337. package/dist/tools/template-copy.d.ts +11 -0
  338. package/dist/tools/template-copy.js +130 -0
  339. package/dist/tools/template-files.d.ts +17 -0
  340. package/dist/tools/template-files.js +74 -0
  341. package/dist/tools/workflow-bundles.js +1 -0
  342. package/dist/tools/workflow-candidate-counts.js +2 -2
  343. package/dist/tools/workflow-candidate-shape.d.ts +3 -0
  344. package/dist/tools/workflow-candidate-shape.js +90 -0
  345. package/dist/tools/workflow-candidate-source.js +15 -1
  346. package/dist/tools/workflow-candidate-syntax.d.ts +2 -2
  347. package/dist/tools/workflow-candidate-syntax.js +24 -94
  348. package/dist/tools/workflow-command.js +1 -2
  349. package/dist/tools/workflow-folders.d.ts +2 -3
  350. package/dist/tools/workflow-folders.js +1 -1
  351. package/package.json +16 -8
  352. package/templates/develop/README.md +12 -0
  353. package/templates/develop/develop/flow-tests.ts +37 -0
  354. package/templates/develop/develop/pull.ts +5 -1
  355. package/templates/develop/develop/refusal-tests.ts +77 -0
  356. package/templates/develop/develop/turn.ts +2 -1
  357. package/templates/idea-to-issue/README.md +27 -0
  358. package/templates/idea-to-issue/idea-to-issue/effects.ts +52 -0
  359. package/templates/idea-to-issue/idea-to-issue/leave.ts +10 -0
  360. package/templates/idea-to-issue/idea-to-issue/next.ts +29 -0
  361. package/templates/idea-to-issue/idea-to-issue/post-person.ts +18 -0
  362. package/templates/idea-to-issue/idea-to-issue/post.ts +50 -0
  363. package/templates/idea-to-issue/idea-to-issue/queue.ts +27 -0
  364. package/templates/idea-to-issue/idea-to-issue/review.ts +42 -0
  365. package/templates/idea-to-issue/idea-to-issue/scenarios.ts +50 -0
  366. package/templates/idea-to-issue/idea-to-issue/turn.ts +40 -0
  367. package/templates/idea-to-issue/idea-to-issue.declaration.json +16 -0
  368. package/templates/idea-to-issue/idea-to-issue.test.ts +79 -0
  369. package/templates/idea-to-issue/idea-to-issue.ts +49 -0
  370. package/templates/idea-to-issue/template.json +81 -0
  371. package/templates/review-day/README.md +97 -0
  372. package/templates/review-day/review-day/critique.ts +58 -0
  373. package/templates/review-day/review-day/decision.ts +29 -0
  374. package/templates/review-day/review-day/facts.ts +69 -0
  375. package/templates/review-day/review-day/label.ts +29 -0
  376. package/templates/review-day/review-day/next.ts +28 -0
  377. package/templates/review-day/review-day/post.ts +27 -0
  378. package/templates/review-day/review-day/report.ts +98 -0
  379. package/templates/review-day/review-day/rules.ts +12 -0
  380. package/templates/review-day/review-day/scenarios.ts +76 -0
  381. package/templates/review-day/review-day/turn.ts +73 -0
  382. package/templates/review-day/review-day.declaration.json +16 -0
  383. package/templates/review-day/review-day.test.ts +362 -0
  384. package/templates/review-day/review-day.ts +37 -0
  385. package/templates/review-day/template.json +56 -0
  386. package/dist/src/cli/watch-start-view.d.ts +0 -18
  387. package/dist/src/cli/watch-start-view.js +0 -77
  388. package/dist/src/steps/decision-gate.d.ts +0 -3
  389. package/dist/workflows/develop-prd.d.ts +0 -7
  390. package/dist/workflows/develop-prd.js +0 -165
  391. package/dist/workflows/develop-ticket.d.ts +0 -9
  392. package/dist/workflows/develop-ticket.js +0 -207
  393. package/dist/workflows/issue-labels.d.ts +0 -16
  394. package/dist/workflows/issue-labels.js +0 -70
  395. package/dist/workflows/ticket-prompts.d.ts +0 -35
  396. package/dist/workflows/ticket-prompts.js +0 -147
@@ -3,6 +3,7 @@ import { issueQueue } from "./issue-queue.js";
3
3
  import { notifyRuns } from "./notify.js";
4
4
  export async function daemonLoops(settings) {
5
5
  const schedules = await workflowSchedules(settings);
6
+ settings.scheduleConnection?.connect(schedules.request);
6
7
  try {
7
8
  const notifier = settings.notifyCommand === undefined
8
9
  ? undefined
@@ -14,7 +15,8 @@ export async function daemonLoops(settings) {
14
15
  ? {}
15
16
  : { intervalMilliseconds: settings.notifyMilliseconds }),
16
17
  });
17
- const queue = settings.queueMilliseconds === undefined
18
+ const queue = settings.queueMilliseconds === undefined ||
19
+ settings.queueWorkflowId === undefined
18
20
  ? undefined
19
21
  : issueQueue({
20
22
  directory: settings.directory,
@@ -1,4 +1,4 @@
1
- import { adapters, recoverCommands, } from "./daemon-adapters.js";
1
+ import { adapters, recoverCommands, recoverClaude, } from "./daemon-adapters.js";
2
2
  import { mkdir, realpath } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { registerStepBridge, sdkStepCall, stepBridge, } from "../host/step-bridge-host.js";
@@ -12,8 +12,9 @@ import { commandEffects } from "./command-journal.js";
12
12
  import { worldDirectory } from "./host-environment.js";
13
13
  import { pinnedWorkflowHandler } from "./workflow-replay.js";
14
14
  import { scheduledDeclarations } from "./workflow-schedules.js";
15
+ import { scheduleConnection } from "./schedule-control.js";
15
16
  import { daemonLoops } from "./daemon-loops.js";
16
- import { ticketRegistrations, ticketRegistrationTable, issueWorkflow, } from "./ticket-registration.js";
17
+ import { ticketRegistrations, ticketRegistrationTable, } from "./ticket-registration.js";
17
18
  export const turnLeaseMilliseconds = 4 * 3_600_000;
18
19
  export const loopMilliseconds = 250;
19
20
  export const bridgeTiming = {
@@ -138,7 +139,12 @@ async function openDaemon(settings) {
138
139
  const { worker, github, runs, handoff, commands } = resources;
139
140
  await mkdir(join(settings.directory, "state"), { recursive: true });
140
141
  await mkdir(settings.runtimeDirectory, { recursive: true, mode: 0o700 });
142
+ const schedules = scheduleConnection();
141
143
  const opening = await startService({
144
+ schedule: schedules.request,
145
+ ...(settings.queueDailySpendLimitUsd === undefined
146
+ ? {}
147
+ : { queueDailySpendLimitUsd: settings.queueDailySpendLimitUsd }),
142
148
  workerHarness: worker.harness,
143
149
  commandEffects: commandEffects(join(settings.directory, "commands")),
144
150
  stateDirectory: join(settings.directory, "state"),
@@ -164,7 +170,7 @@ async function openDaemon(settings) {
164
170
  await closeWorkers(resources);
165
171
  return opening;
166
172
  }
167
- const running = { ...resources, service: opening.service };
173
+ const running = { ...resources, service: opening.service, schedules };
168
174
  await recoverWorkers(running, given.promise);
169
175
  return {
170
176
  kind: "started",
@@ -173,7 +179,9 @@ async function openDaemon(settings) {
173
179
  }
174
180
  async function recoverWorkers(resources, ports) {
175
181
  try {
176
- await recoverCommands(resources.commands, await ports);
182
+ const host = await ports;
183
+ await recoverCommands(resources.commands, host);
184
+ await recoverClaude(resources.worker, host);
177
185
  }
178
186
  catch (error) {
179
187
  await Promise.allSettled([
@@ -195,6 +203,7 @@ async function runningDaemon(settings, resources) {
195
203
  await github.scan();
196
204
  const loops = await daemonLoops({
197
205
  ...settings,
206
+ scheduleConnection: resources.schedules,
198
207
  submit: service.worker.submit,
199
208
  queueWorkflowId: queueWorkflowId(settings),
200
209
  }).catch(async (error) => {
@@ -212,6 +221,5 @@ async function runningDaemon(settings, resources) {
212
221
  };
213
222
  }
214
223
  function queueWorkflowId(settings) {
215
- return (settings.declarations?.find((declaration) => declaration.queue === true)
216
- ?.workflowId ?? issueWorkflow);
224
+ return settings.declarations?.find((declaration) => declaration.queue === true)?.workflowId;
217
225
  }
@@ -0,0 +1,2 @@
1
+ import { type Operation } from "./github-operation.ts";
2
+ export declare function attachAsset(operation: Operation): Promise<unknown>;
@@ -0,0 +1,149 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { copiedAttachment, openedAttachment } from "./attachment-file.js";
4
+ import { attachmentFolder } from "./attachments.js";
5
+ import { repositoryName } from "./github-issue-methods.js";
6
+ import { issuePrivacy, operatorPrivateTexts } from "./github-issue-privacy.js";
7
+ import { field, repositoryPath, text, } from "./github-operation.js";
8
+ import { githubRead } from "./github-retry.js";
9
+ function isList(value) {
10
+ return Array.isArray(value);
11
+ }
12
+ function validName(name) {
13
+ return /^[\w-][\w.-]*\.(?:mp4|webm|png)$/.test(name) && !name.includes("..");
14
+ }
15
+ function validRelease(release) {
16
+ return /^[a-z][a-z0-9-]{0,63}$/.test(release) && !/^v\d/.test(release);
17
+ }
18
+ function attachmentOf(operation) {
19
+ const number = operation.payload["number"];
20
+ const name = text(operation, "name");
21
+ const release = text(operation, "release");
22
+ const comment = text(operation, "comment");
23
+ if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 1)
24
+ throw new Error("invalid-payload");
25
+ if (!validName(name) || !validRelease(release) || comment.length > 4000)
26
+ throw new Error("invalid-payload");
27
+ return { number, name, release, comment };
28
+ }
29
+ function attachedFile(operation, name) {
30
+ return openedAttachment({
31
+ folder: attachmentFolder({
32
+ worktrees: operation.settings.worktrees,
33
+ target: operation.target,
34
+ }),
35
+ name,
36
+ });
37
+ }
38
+ async function pages(operation, path) {
39
+ const reply = JSON.parse(await githubRead(operation, ["api", "--paginate", "--slurp", path]));
40
+ if (!isList(reply))
41
+ throw new Error("github-call-failed");
42
+ return reply.flatMap((page) => {
43
+ if (!isList(page))
44
+ throw new Error("github-call-failed");
45
+ return page;
46
+ });
47
+ }
48
+ async function releaseOf(operation, tag) {
49
+ const releases = await pages(operation, `${repositoryPath}/releases?per_page=100`);
50
+ return releases.find((item) => field(item, "tag_name") === tag);
51
+ }
52
+ async function ensuredRelease(operation, release) {
53
+ if ((await releaseOf(operation, release)) !== undefined)
54
+ return;
55
+ try {
56
+ await operation.calls.gh([
57
+ "release",
58
+ "create",
59
+ release,
60
+ "--prerelease",
61
+ "--latest=false",
62
+ "--title",
63
+ "Pull request demos",
64
+ "--notes",
65
+ "Demo videos of pull requests. Cloving uploads them.",
66
+ ]);
67
+ }
68
+ catch (error) {
69
+ if ((await releaseOf(operation, release)) === undefined)
70
+ throw error;
71
+ }
72
+ }
73
+ async function uploaded(operation, attachment) {
74
+ const { release } = attachment;
75
+ await ensuredRelease(operation, release);
76
+ const asset = `pr-${String(attachment.number)}-${attachment.name}`;
77
+ const copy = join(operation.folder, asset);
78
+ try {
79
+ await copiedAttachment(attachment.file, copy);
80
+ await operation.calls.gh(["release", "upload", release, copy, "--clobber"]);
81
+ }
82
+ finally {
83
+ await rm(copy, { force: true });
84
+ }
85
+ const assets = field(await releaseOf(operation, release), "assets");
86
+ const found = isList(assets)
87
+ ? assets.find((item) => field(item, "name") === asset)
88
+ : undefined;
89
+ const url = field(found, "browser_download_url");
90
+ if (typeof url !== "string" || !url.startsWith("https://"))
91
+ throw new Error("github-call-failed");
92
+ return url;
93
+ }
94
+ async function ownLogin(operation) {
95
+ const user = JSON.parse(await githubRead(operation, ["api", "user"]));
96
+ const login = field(user, "login");
97
+ if (typeof login !== "string" || login === "")
98
+ throw new Error("github-call-failed");
99
+ return login;
100
+ }
101
+ async function commented(operation, attachment) {
102
+ const { number, name, url } = attachment;
103
+ const marker = `<!-- cloving-attachment:${String(number)}:${name} -->`;
104
+ const body = `${attachment.comment}\n\n[${name}](${url})\n\n${marker}`;
105
+ const comments = await pages(operation, `${repositoryPath}/issues/${String(number)}/comments?per_page=100`);
106
+ const login = await ownLogin(operation);
107
+ const known = comments.find((item) => {
108
+ const existing = field(item, "body");
109
+ return (typeof existing === "string" &&
110
+ existing.includes(marker) &&
111
+ field(field(item, "user"), "login") === login);
112
+ });
113
+ const id = field(known, "id");
114
+ if (typeof id === "number") {
115
+ await operation.calls.gh([
116
+ "api",
117
+ "-X",
118
+ "PATCH",
119
+ `${repositoryPath}/issues/comments/${String(id)}`,
120
+ "-f",
121
+ `body=${body}`,
122
+ ]);
123
+ return;
124
+ }
125
+ await operation.calls.gh([
126
+ "api",
127
+ `${repositoryPath}/issues/${String(number)}/comments`,
128
+ "-f",
129
+ `body=${body}`,
130
+ ]);
131
+ }
132
+ export async function attachAsset(operation) {
133
+ const attachment = attachmentOf(operation);
134
+ operatorPrivateTexts(operation.settings);
135
+ const repository = repositoryName(await operation.calls.gh(["repo", "view", "--json", "nameWithOwner"]));
136
+ issuePrivacy(attachment.comment, {
137
+ settings: operation.settings,
138
+ repository,
139
+ });
140
+ const file = await attachedFile(operation, attachment.name);
141
+ try {
142
+ const url = await uploaded(operation, { ...attachment, file });
143
+ await commented(operation, { ...attachment, url });
144
+ return { number: attachment.number, url };
145
+ }
146
+ finally {
147
+ await file.close();
148
+ }
149
+ }
@@ -0,0 +1,6 @@
1
+ export declare function triggerKind(trigger: unknown): "push" | "event";
2
+ export declare function pullRequestTrigger(source: string): unknown;
3
+ export declare function workflowApplies(trigger: unknown, context: {
4
+ readonly branch: string;
5
+ readonly paths: readonly string[];
6
+ }): boolean;
@@ -0,0 +1,138 @@
1
+ import { parse } from "yaml";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ export function triggerKind(trigger) {
6
+ if (!isRecord(trigger))
7
+ return "push";
8
+ const types = patterns(trigger["types"]);
9
+ return types === undefined || types.includes("synchronize")
10
+ ? "push"
11
+ : "event";
12
+ }
13
+ export function pullRequestTrigger(source) {
14
+ try {
15
+ const document = parse(source, { merge: true });
16
+ if (!isRecord(document) || !("on" in document))
17
+ return true;
18
+ const events = document["on"];
19
+ if (typeof events === "string")
20
+ return events === "pull_request";
21
+ if (Array.isArray(events))
22
+ return events.includes("pull_request");
23
+ if (isRecord(events))
24
+ return "pull_request" in events ? events["pull_request"] : false;
25
+ return true;
26
+ }
27
+ catch {
28
+ return true;
29
+ }
30
+ }
31
+ function patternToken(token) {
32
+ if (token === "**/")
33
+ return { kind: "directory" };
34
+ if (token === "*" || token === "**")
35
+ return {
36
+ kind: "character",
37
+ matcher: token === "*" ? /[^/]/ : /[\s\S]/,
38
+ optional: true,
39
+ repeat: true,
40
+ };
41
+ const literal = token.startsWith("\\") ? token.slice(1) : token;
42
+ const source = token.startsWith("[")
43
+ ? token
44
+ : literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45
+ return {
46
+ kind: "character",
47
+ matcher: new RegExp(`^${source}$`),
48
+ optional: false,
49
+ repeat: false,
50
+ };
51
+ }
52
+ function patternTokens(source) {
53
+ const tokens = [];
54
+ for (const raw of source.match(/\*\*\/|\*\*|\*|\[[a-zA-Z0-9-]+\]|\\[\s\S]|[\s\S]/g) ?? []) {
55
+ if (raw !== "?" && raw !== "+") {
56
+ tokens.push(patternToken(raw));
57
+ continue;
58
+ }
59
+ const previous = tokens.pop();
60
+ if (previous?.kind !== "character" || previous.optional || previous.repeat)
61
+ throw new Error("invalid-workflow-filter");
62
+ tokens.push({ ...previous, optional: raw === "?", repeat: raw === "+" });
63
+ }
64
+ return tokens;
65
+ }
66
+ function advanceDirectory({ value, previous, }) {
67
+ let reachable = false;
68
+ return Array.from({ length: value.length + 1 }, (_, index) => {
69
+ const matches = previous[index] === true || (reachable && value[index - 1] === "/");
70
+ reachable ||= previous[index] === true;
71
+ return matches;
72
+ });
73
+ }
74
+ function advancePattern(token, input) {
75
+ const { value, previous } = input;
76
+ const next = [];
77
+ for (let index = 0; index <= value.length; index += 1) {
78
+ const consumes = index > 0 && token.matcher.test(value[index - 1] ?? "");
79
+ next[index] =
80
+ (token.optional && previous[index] === true) ||
81
+ (consumes &&
82
+ (previous[index - 1] === true ||
83
+ (token.repeat && next[index - 1] === true)));
84
+ }
85
+ return next;
86
+ }
87
+ function matchesPattern(value, source) {
88
+ let previous = [true];
89
+ for (const token of patternTokens(source))
90
+ previous =
91
+ token.kind === "directory"
92
+ ? advanceDirectory({ value, previous })
93
+ : advancePattern(token, { value, previous });
94
+ return previous[value.length] === true;
95
+ }
96
+ function selected(value, patterns) {
97
+ let included = false;
98
+ for (const pattern of patterns) {
99
+ const negative = pattern.startsWith("!");
100
+ const source = negative ? pattern.slice(1) : pattern;
101
+ try {
102
+ if (matchesPattern(value, source))
103
+ included = !negative;
104
+ }
105
+ catch {
106
+ return undefined;
107
+ }
108
+ }
109
+ return included;
110
+ }
111
+ function patterns(value) {
112
+ if (!Array.isArray(value) || value.length === 0)
113
+ return undefined;
114
+ return value.every((item) => typeof item === "string")
115
+ ? value
116
+ : undefined;
117
+ }
118
+ function allows(values, filters) {
119
+ const include = patterns(filters.include);
120
+ const ignore = patterns(filters.ignore);
121
+ if (include !== undefined &&
122
+ !values.some((value) => selected(value, include) !== false))
123
+ return false;
124
+ return (ignore === undefined ||
125
+ values.some((value) => selected(value, ignore) !== true));
126
+ }
127
+ export function workflowApplies(trigger, context) {
128
+ if (!isRecord(trigger))
129
+ return trigger !== false;
130
+ return (allows([context.branch], {
131
+ include: trigger["branches"],
132
+ ignore: trigger["branches-ignore"],
133
+ }) &&
134
+ allows(context.paths, {
135
+ include: trigger["paths"],
136
+ ignore: trigger["paths-ignore"],
137
+ }));
138
+ }
@@ -0,0 +1,10 @@
1
+ import { type Operation } from "./github-operation.ts";
2
+ export interface ExpectedWorkflow {
3
+ readonly path: string;
4
+ readonly kind: "push" | "event";
5
+ }
6
+ export declare function pullRequestWorkflows(operation: Operation, commit: string): Promise<readonly ExpectedWorkflow[]>;
7
+ export declare function pendingWorkflows(operation: Operation, input: {
8
+ readonly commit: string;
9
+ readonly workflows: readonly ExpectedWorkflow[];
10
+ }): Promise<readonly string[]>;
@@ -0,0 +1,148 @@
1
+ import { pullRequestTrigger, triggerKind, workflowApplies, } from "./github-ci-filters.js";
2
+ import { field, repositoryPath } from "./github-operation.js";
3
+ import { githubRead } from "./github-retry.js";
4
+ async function pullBase(operation) {
5
+ const pull = JSON.parse(await githubRead(operation, [
6
+ "api",
7
+ `${repositoryPath}/pulls/${String(operation.payload["number"])}`,
8
+ ]));
9
+ if (typeof pull !== "object" || pull === null || !("base" in pull))
10
+ throw new Error("github-call-failed");
11
+ const baseData = pull.base;
12
+ if (typeof baseData !== "object" || baseData === null)
13
+ throw new Error("github-call-failed");
14
+ const base = field(baseData, "sha");
15
+ const branch = field(baseData, "ref");
16
+ if (typeof base !== "string" ||
17
+ !/^[0-9a-f]{40,64}$/.test(base) ||
18
+ typeof branch !== "string")
19
+ throw new Error("github-call-failed");
20
+ return { base, branch };
21
+ }
22
+ async function appliesToPull(operation, input) {
23
+ const { commit, trigger } = input;
24
+ if (trigger === false)
25
+ return false;
26
+ if (typeof trigger !== "object" || trigger === null)
27
+ return true;
28
+ if (!["paths", "paths-ignore", "branches", "branches-ignore"].some((key) => Object.hasOwn(trigger, key)))
29
+ return true;
30
+ const { base, branch } = await pullBase(operation);
31
+ await operation.calls.git(operation.settings.repository, [
32
+ "fetch",
33
+ "--quiet",
34
+ "--no-tags",
35
+ "origin",
36
+ base,
37
+ ]);
38
+ const paths = await operation.calls.git(operation.settings.repository, [
39
+ "diff",
40
+ "--name-only",
41
+ "-z",
42
+ `${base}...${commit}`,
43
+ ]);
44
+ return workflowApplies(trigger, {
45
+ branch,
46
+ paths: paths.split("\0").filter((path) => path !== ""),
47
+ });
48
+ }
49
+ export async function pullRequestWorkflows(operation, commit) {
50
+ const read = (parts) => operation.calls.git(operation.settings.repository, parts);
51
+ const paths = await read([
52
+ "ls-tree",
53
+ "-r",
54
+ "--name-only",
55
+ "-z",
56
+ commit,
57
+ "--",
58
+ ".github/workflows",
59
+ ]);
60
+ const workflows = [];
61
+ for (const path of paths.split("\0")) {
62
+ if (!/^\.github\/workflows\/[^/]+\.ya?ml$/.test(path))
63
+ continue;
64
+ const trigger = pullRequestTrigger(await read(["show", `${commit}:${path}`]));
65
+ if (await appliesToPull(operation, { commit, trigger }))
66
+ workflows.push({ path, kind: triggerKind(trigger) });
67
+ }
68
+ if (workflows.length === 0)
69
+ return workflows;
70
+ const inactive = await inactiveWorkflows(operation);
71
+ return workflows.filter((workflow) => !inactive.has(workflow.path));
72
+ }
73
+ function isList(value) {
74
+ return Array.isArray(value);
75
+ }
76
+ async function actionPages(operation, request) {
77
+ const pages = JSON.parse(await githubRead(operation, ["api", "--paginate", "--slurp", request.path]));
78
+ if (!isList(pages) || pages.length === 0)
79
+ throw new Error("github-call-failed");
80
+ return pages.flatMap((page) => {
81
+ if (typeof page !== "object" || page === null)
82
+ throw new Error("github-call-failed");
83
+ const items = field(page, request.key);
84
+ if (!isList(items))
85
+ throw new Error("github-call-failed");
86
+ return items;
87
+ });
88
+ }
89
+ async function inactiveWorkflows(operation) {
90
+ const workflows = await actionPages(operation, {
91
+ path: `${repositoryPath}/actions/workflows?per_page=100`,
92
+ key: "workflows",
93
+ });
94
+ const inactive = new Set();
95
+ for (const workflow of workflows) {
96
+ if (typeof workflow !== "object" || workflow === null)
97
+ throw new Error("github-call-failed");
98
+ const path = field(workflow, "path");
99
+ const state = field(workflow, "state");
100
+ if (typeof path !== "string" || typeof state !== "string")
101
+ throw new Error("github-call-failed");
102
+ if (state !== "active")
103
+ inactive.add(path);
104
+ }
105
+ return inactive;
106
+ }
107
+ function isWorkflowRun(value) {
108
+ if (typeof value !== "object" || value === null)
109
+ return false;
110
+ const conclusion = field(value, "conclusion");
111
+ return (typeof field(value, "id") === "number" &&
112
+ ["path", "head_sha", "event", "status"].every((key) => typeof field(value, key) === "string") &&
113
+ (conclusion === null || typeof conclusion === "string"));
114
+ }
115
+ async function workflowRuns(operation, commit) {
116
+ const runs = await actionPages(operation, {
117
+ path: `${repositoryPath}/actions/runs?head_sha=${commit}&event=pull_request&per_page=100`,
118
+ key: "workflow_runs",
119
+ });
120
+ if (!runs.every(isWorkflowRun))
121
+ throw new Error("github-call-failed");
122
+ return runs;
123
+ }
124
+ export async function pendingWorkflows(operation, input) {
125
+ if (input.workflows.length === 0)
126
+ return [];
127
+ const paths = input.workflows.map((workflow) => workflow.path);
128
+ const latest = new Map();
129
+ for (const run of await workflowRuns(operation, input.commit)) {
130
+ if (run.head_sha !== input.commit || run.event !== "pull_request")
131
+ continue;
132
+ const path = paths
133
+ .filter((expected) => run.path === expected || run.path.startsWith(`${expected}@`))
134
+ .sort((left, right) => right.length - left.length)[0];
135
+ if (path === undefined)
136
+ continue;
137
+ if (run.id > (latest.get(path)?.id ?? -1))
138
+ latest.set(path, run);
139
+ }
140
+ const pending = input.workflows.filter((workflow) => {
141
+ const run = latest.get(workflow.path);
142
+ if (run === undefined)
143
+ return workflow.kind === "push";
144
+ return (run.status !== "completed" ||
145
+ !["success", "neutral", "skipped"].includes(run.conclusion ?? ""));
146
+ });
147
+ return pending.length === 0 ? [] : ["waiting for GitHub Actions checks"];
148
+ }
@@ -2,6 +2,7 @@ import { settingsEnvironment } from "../cli/project-settings.js";
2
2
  import { setTimeout as delay } from "node:timers/promises";
3
3
  import { githubText } from "./github-calls.js";
4
4
  import { githubRead } from "./github-retry.js";
5
+ import { pendingWorkflows, pullRequestWorkflows, } from "./github-ci-workflows.js";
5
6
  import { ciWait, workflowAttempt, attemptChecks, rerunCi, attemptOutput, } from "./github-ci-rerun.js";
6
7
  import { field, githubTiming, repositoryPath, } from "./github-operation.js";
7
8
  function isCheckRun(value) {
@@ -203,16 +204,29 @@ async function ciFailureOutput(operation, input) {
203
204
  current,
204
205
  ]);
205
206
  }
206
- export async function waitForCi(operation, commit) {
207
- const { pollMilliseconds: poll, ciGraceMilliseconds: grace, ciLimitMilliseconds: limit, } = {
207
+ async function ciSettings(operation, commit) {
208
+ const timing = {
208
209
  ...githubTiming,
209
210
  ciGraceMilliseconds: 60_000,
210
211
  ciLimitMilliseconds: 5_400_000,
211
212
  ...operation.settings.timing,
212
213
  };
214
+ const required = requiredChecks(operation);
215
+ const workflows = required === undefined ? await pullRequestWorkflows(operation, commit) : [];
216
+ return {
217
+ required,
218
+ workflows,
219
+ poll: timing.pollMilliseconds,
220
+ limit: timing.ciLimitMilliseconds,
221
+ grace: workflows.some((workflow) => workflow.kind === "push")
222
+ ? Infinity
223
+ : timing.ciGraceMilliseconds,
224
+ };
225
+ }
226
+ export async function waitForCi(operation, commit) {
213
227
  const wait = await ciWait(operation, commit);
214
228
  const started = wait.startedAt;
215
- const required = requiredChecks(operation);
229
+ const { required, workflows, grace, poll, limit } = await ciSettings(operation, commit);
216
230
  const names = new Set(required);
217
231
  for (;;) {
218
232
  const checks = latestRuns(await checkRuns(operation, commit), required);
@@ -232,7 +246,11 @@ export async function waitForCi(operation, commit) {
232
246
  failed: retried.failures,
233
247
  }),
234
248
  };
235
- const pending = [...pendingChecks(checks, names), ...retried.pending];
249
+ const pending = [
250
+ ...pendingChecks(checks, names),
251
+ ...retried.pending,
252
+ ...(await pendingWorkflows(operation, { commit, workflows })),
253
+ ];
236
254
  if (pending.length === 0 &&
237
255
  (required !== undefined ||
238
256
  names.size > 0 ||
@@ -1,3 +1,5 @@
1
1
  import { type Operation } from "./github-operation.ts";
2
+ export declare function repositoryName(reply: string): string;
3
+ export declare function captureDraft(operation: Operation): Promise<unknown>;
2
4
  export declare function createIssue(operation: Operation): Promise<unknown>;
3
5
  export declare function discard(operation: Operation): Promise<unknown>;
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
2
3
  import { lstat, readFile } from "node:fs/promises";
3
4
  import { basename, join } from "node:path";
4
5
  import { existingTree, removeWorktree, repositoryPath, text, } from "./github-operation.js";
@@ -30,7 +31,7 @@ function issuePage(line) {
30
31
  throw new Error("github-call-failed");
31
32
  return page.map((issue) => issueOf(issue));
32
33
  }
33
- function repositoryName(reply) {
34
+ export function repositoryName(reply) {
34
35
  const repository = JSON.parse(reply);
35
36
  if (typeof repository !== "object" ||
36
37
  repository === null ||
@@ -50,6 +51,28 @@ async function draftOf(operation) {
50
51
  throw new Error("draft-not-available");
51
52
  return readFile(path, "utf8");
52
53
  }
54
+ function draftDigest(text) {
55
+ return "sha256:" + createHash("sha256").update(text).digest("hex");
56
+ }
57
+ export async function captureDraft(operation) {
58
+ const text = await draftOf(operation);
59
+ const capture = { text, digest: draftDigest(text) };
60
+ if (Buffer.byteLength(JSON.stringify(capture)) > 65_536)
61
+ throw new Error("draft-too-large");
62
+ return capture;
63
+ }
64
+ function checkDraftDigest(operation, draft) {
65
+ if (!Object.hasOwn(operation.payload, "expectedDigests"))
66
+ return;
67
+ const digests = operation.payload["expectedDigests"];
68
+ if (!Array.isArray(digests) ||
69
+ digests.length === 0 ||
70
+ !digests.every((digest) => typeof digest === "string" && /^sha256:[0-9a-f]{64}$/.test(digest)))
71
+ throw new Error("invalid-payload");
72
+ const current = draftDigest(draft);
73
+ if (!digests.every((digest) => digest === current))
74
+ throw new Error("draft-changed");
75
+ }
53
76
  async function knownIssue(operation, marker) {
54
77
  const output = await operation.calls.gh([
55
78
  "api",
@@ -62,7 +85,10 @@ async function knownIssue(operation, marker) {
62
85
  }
63
86
  export async function createIssue(operation) {
64
87
  operatorPrivateTexts(operation.settings);
65
- const draft = await draftOf(operation);
88
+ const draft = Object.hasOwn(operation.payload, "text")
89
+ ? reportText(operation)
90
+ : await draftOf(operation);
91
+ checkDraftDigest(operation, draft);
66
92
  const [heading = "", ...lines] = draft.split("\n");
67
93
  if (!heading.startsWith("# ") || heading.slice(2).trim() === "")
68
94
  throw new Error("invalid-draft");
@@ -89,6 +115,15 @@ export async function createIssue(operation) {
89
115
  const url = printed.trim().split("\n").at(-1) ?? "";
90
116
  return issueFromUrl(url);
91
117
  }
118
+ function reportText(operation) {
119
+ if (Object.hasOwn(operation.payload, "draft") ||
120
+ Object.hasOwn(operation.payload, "expectedDigests"))
121
+ throw new Error("invalid-payload");
122
+ const report = text(operation, "text");
123
+ if (Buffer.byteLength(report) > 65536)
124
+ throw new Error("draft-too-large");
125
+ return report;
126
+ }
92
127
  export async function discard(operation) {
93
128
  const place = treePlace({
94
129
  worktrees: operation.settings.worktrees,