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
@@ -5,8 +5,7 @@ const endings = [
5
5
  ];
6
6
  const review = { open: "gate.opened", close: "gate.decided" };
7
7
  const effect = { open: "effect.requested", close: "effect.reported" };
8
- const issueWorkflow = "develop-issue";
9
- export const issueSteps = [
8
+ export const pullRequestSteps = [
10
9
  { name: "Work tree", operation: "github-prepare" },
11
10
  { name: "Agent turn", operation: "claude-turn" },
12
11
  { name: "Pull request", operation: "github-open-pr" },
@@ -79,24 +78,82 @@ function operationOf(event) {
79
78
  return [];
80
79
  }
81
80
  }
81
+ export function pullRequestStepOf(event) {
82
+ const scope = operationOf(record(event));
83
+ return pullRequestSteps.find((step) => step.operation === scope[4] ||
84
+ (step.name === "Code review" && scope[3] === "review") ||
85
+ (step.name === "Merge" && scope[3] === "merge"));
86
+ }
87
+ function stepEffects(events, context) {
88
+ const { step, round } = context;
89
+ const repeated = roundOperations.includes(step.operation);
90
+ const currentTurn = events.findLastIndex((event) => event["type"] === effect.open && operationOf(event)[4] === "claude-turn");
91
+ const requested = new Set(events
92
+ .slice(currentTurn + 1)
93
+ .filter((event) => event["type"] === effect.open)
94
+ .map((event) => record(event["payload"])["operationId"]));
95
+ return events.filter((event) => {
96
+ if (pullRequestStepOf(event) !== step)
97
+ return false;
98
+ if (!repeated)
99
+ return true;
100
+ const scope = operationOf(event);
101
+ return roundOperations.includes(String(scope[4]))
102
+ ? scope[1] === round
103
+ : requested.has(record(event["payload"])["operationId"]);
104
+ });
105
+ }
106
+ export function stageOf(event, stages) {
107
+ const fields = record(event);
108
+ if ([effect.open, effect.close].includes(String(fields["type"]))) {
109
+ const operation = operationOf(fields)[4];
110
+ return stages.find((stage) => stage.operations?.some((name) => name === operation));
111
+ }
112
+ const payload = record(fields["payload"]);
113
+ return (stages.find((stage) => stage.gateIds?.some((name) => name === payload["gateId"])) ??
114
+ stages.find((stage) => stage.gateKinds?.some((name) => name === payload["gateKind"])));
115
+ }
116
+ function declaredSpans(events, stages) {
117
+ return stages.map((stage) => {
118
+ const index = events.findLastIndex((event) => [effect.open, review.open].includes(String(event["type"])) &&
119
+ stageOf(event, stages) === stage);
120
+ const opening = events[index];
121
+ const pair = opening?.["type"] === review.open ? review : effect;
122
+ const identity = pair === review ? "gateId" : "operationId";
123
+ const key = record(opening?.["payload"])[identity];
124
+ const closing = events
125
+ .slice(index + 1)
126
+ .findLast((event) => event["type"] === pair.close &&
127
+ key !== undefined &&
128
+ record(event["payload"])[identity] === key);
129
+ const payload = record(closing?.["payload"]);
130
+ const failed = unfinished.includes(String(payload["outcome"])) ||
131
+ payload["decision"] === "denied";
132
+ return {
133
+ name: stage.label,
134
+ startedAt: timeOf(opening),
135
+ endedAt: timeOf(closing),
136
+ ending: failed ? "failed" : "done",
137
+ };
138
+ });
139
+ }
82
140
  function currentRound(events) {
83
141
  const requested = events.findLast((event) => event["type"] === effect.open && operationOf(event)[4] === "claude-turn");
84
142
  const round = operationOf(requested)[1];
85
143
  return typeof round === "number" ? round : 1;
86
144
  }
87
- function spans(events, workflowId) {
88
- if (workflowId !== issueWorkflow)
145
+ function spans(events, workflow) {
146
+ if (workflow.stages !== undefined)
147
+ return [...admission(events), ...declaredSpans(events, workflow.stages)];
148
+ if (workflow.pullRequest == null)
89
149
  return ticketSpans(events);
90
150
  const effects = events.filter((event) => [effect.open, effect.close].includes(String(event["type"])));
91
151
  const round = currentRound(effects);
92
152
  return [
93
153
  ...admission(events),
94
- ...issueSteps.map((step) => {
154
+ ...pullRequestSteps.map((step) => {
95
155
  const repeated = roundOperations.includes(step.operation);
96
- const own = effects.filter((event) => {
97
- const scope = operationOf(event);
98
- return scope[4] === step.operation && (!repeated || scope[1] === round);
99
- });
156
+ const own = stepEffects(effects, { step, round });
100
157
  const last = own.findLast((event) => event["type"] === effect.close);
101
158
  const outcome = String(record(last?.["payload"])["outcome"]);
102
159
  return {
@@ -132,9 +189,9 @@ function advanced(phases) {
132
189
  ? { ...item, state: "active" }
133
190
  : item);
134
191
  }
135
- export function phasesOf(events, workflowId = "") {
192
+ export function phasesOf(events, workflow = {}) {
136
193
  const known = events.map(record);
137
- const steps = spans(known, workflowId);
194
+ const steps = spans(known, workflow);
138
195
  const [end] = ends(known);
139
196
  if (end !== undefined)
140
197
  return [...steps.map((span) => phase(span, "failed")), end];
@@ -2,7 +2,7 @@ function observedCard(context, card) {
2
2
  if (card.completionMark?.text !== "Needs attention")
3
3
  return card;
4
4
  const run = context.model.runs.find((run) => run.runId === card.runId);
5
- if (run?.workflowId !== "develop-issue" ||
5
+ if (run === undefined ||
6
6
  run.state !== "completed" ||
7
7
  run.outcome !== "blocked" ||
8
8
  run.pullRequest === null)
@@ -6,6 +6,7 @@ export interface WatchCommandOutput {
6
6
  readonly errorCode: string | null;
7
7
  }
8
8
  export interface WatchCommandRequest {
9
+ readonly outputBytes?: number;
9
10
  readonly command: readonly string[];
10
11
  readonly directory: string;
11
12
  readonly signal: AbortSignal;
@@ -23,6 +23,11 @@ function list(value) {
23
23
  function count(value) {
24
24
  return typeof value === "number" ? value : null;
25
25
  }
26
+ function methodScope(method, operation) {
27
+ if (operation[3] === "pull-request")
28
+ return operation[4] === `github-${method}`;
29
+ return (method === "merge" && operation[3] === "merge" && operation[4] === "merge");
30
+ }
26
31
  function attributed(started, operation) {
27
32
  const stage = list(operation[5]);
28
33
  const checked = operationIdentity({
@@ -37,8 +42,7 @@ function attributed(started, operation) {
37
42
  });
38
43
  return (checked.kind === "valid" &&
39
44
  checked.value === JSON.stringify(operation) &&
40
- operation[3] === "pull-request" &&
41
- operation[4] === `github-${text(started["method"])}` &&
45
+ methodScope(text(started["method"]), operation) &&
42
46
  (started["runId"] === undefined || started["runId"] === operation[2]));
43
47
  }
44
48
  function githubOf(records) {
@@ -47,22 +51,28 @@ function githubOf(records) {
47
51
  const result = record(records.result);
48
52
  const completed = result["outcome"] === "completed";
49
53
  return {
50
- runId: text(operation[2]),
54
+ runId: started["runId"] === undefined || started["runId"] === operation[2]
55
+ ? text(operation[2])
56
+ : "",
51
57
  round: Number(operation[1]),
58
+ startedAt: Date.parse(text(started["startedAt"])) || 0,
52
59
  method: text(started["method"]),
53
60
  completed,
54
61
  attributed: attributed(started, operation) && !Object.hasOwn(result, "reason"),
55
62
  report: completed ? record(parsed(text(result["result"]))) : {},
56
63
  };
57
64
  }
58
- function pullRequestOf(results) {
65
+ function pullRequestOf(results, repository) {
59
66
  const opened = results
60
- .filter((item) => item.method === "open-pr")
67
+ .filter((item) => item.method === "open-pr" && item.completed)
61
68
  .map((item) => item.report)
62
69
  .findLast((report) => text(report["url"]).startsWith("https://"));
63
70
  if (opened === undefined)
64
71
  return null;
65
- return { number: Number(opened["number"]), url: text(opened["url"]) };
72
+ const pull = { number: Number(opened["number"]), url: text(opened["url"]) };
73
+ return repository === null || prIdentity(repository, pull) !== null
74
+ ? pull
75
+ : null;
66
76
  }
67
77
  function reviewsOf(results) {
68
78
  return results
@@ -115,8 +125,8 @@ export function pullOf(inputs, runId) {
115
125
  const results = inputs.records
116
126
  .map(githubOf)
117
127
  .filter((item) => item.runId === runId)
118
- .toSorted((first, second) => first.round - second.round);
119
- const pullRequest = pullRequestOf(results);
128
+ .toSorted((first, second) => first.round - second.round || first.startedAt - second.startedAt);
129
+ const pullRequest = pullRequestOf(results, inputs.repository);
120
130
  const mergeProof = mergeProofOf(results, {
121
131
  repository: inputs.repository,
122
132
  pullRequest,
@@ -1,4 +1,5 @@
1
1
  import { type PrRepository } from "./watch-pr-identity.ts";
2
+ import { type ShownSchedules } from "./schedule-render.ts";
2
3
  import { type WorkflowStages } from "../workflow-declaration.ts";
3
4
  import { type Fields } from "../protocol.ts";
4
5
  import { type WorkflowRefusal } from "../workflow-refusals.ts";
@@ -16,6 +17,7 @@ interface ReadRun {
16
17
  readonly events: readonly ReadEvent[];
17
18
  }
18
19
  export interface WatchRead {
20
+ readonly schedules?: ShownSchedules;
19
21
  readonly queue?: Fields;
20
22
  readonly service: string;
21
23
  readonly workflows: readonly string[];
@@ -1,4 +1,5 @@
1
1
  import { establishedIssue } from "./watch-pr-identity.js";
2
+ import { shownSchedules } from "./schedule-render.js";
2
3
  import { isWorkflowStages, } from "../workflow-declaration.js";
3
4
  import { record } from "../protocol.js";
4
5
  import { send } from "./client.js";
@@ -86,7 +87,6 @@ async function creationOf(reader, runId) {
86
87
  function matchingCompletion(status, context) {
87
88
  const issue = establishedIssue(context.repository, status["issueIdentity"]);
88
89
  return (status.state === "completed" &&
89
- status["workflowId"] === "develop-issue" &&
90
90
  record(status["outcome"])?.["kind"] === "handoff_requested" &&
91
91
  record(status["outcome"])?.["ticketStatus"] !== "partial" &&
92
92
  record(status["outcome"])?.["issueUpdate"] !== "failed" &&
@@ -127,20 +127,17 @@ async function completionOf(reader, search) {
127
127
  reader.completions.set(runId, { ...known, creation });
128
128
  return { status: known.status, events: creation === null ? [] : [creation] };
129
129
  }
130
- function refreshedLinks(run, status) {
131
- const links = Object.fromEntries(["parentRunId", "chainId", "childRunIds"]
130
+ function refreshedStatus(run, status) {
131
+ const links = Object.fromEntries(["parentRunId", "chainId", "childRunIds", "commandWaits"]
132
132
  .filter((name) => Object.hasOwn(status, name))
133
133
  .map((name) => [name, status[name]]));
134
- return {
135
- ...run,
136
- status: { ...run.status, ...links },
137
- };
134
+ return { ...run, status: { ...run.status, ...links } };
138
135
  }
139
136
  async function detailed(reader, summary) {
140
137
  const { runId, latestSequence: sequence } = summary;
141
138
  const known = reader.details.get(runId);
142
139
  if (known !== undefined && known.sequence === sequence)
143
- return refreshedLinks(known.value, summary);
140
+ return refreshedStatus(known.value, summary);
144
141
  const shown = await asked(reader, {
145
142
  operation: "status",
146
143
  parameters: { runId },
@@ -181,13 +178,14 @@ function stoppedIssues(runs, context) {
181
178
  const stopped = runs
182
179
  .map((run) => run.status)
183
180
  .filter((status) => recentRun(status.latestAt, context.now) &&
184
- status["workflowId"] === "develop-issue" &&
185
181
  (["failed", "cancelled"].includes(status.state) ||
186
182
  (status.state === "completed" &&
187
183
  record(status["outcome"])?.["kind"] === "blocked")))
188
- .toSorted((first, second) => Date.parse(second.latestAt ?? "") - Date.parse(first.latestAt ?? ""))
189
- .slice(0, 20);
190
- return new Set(stopped.flatMap((status) => {
184
+ .toSorted((first, second) => Date.parse(second.latestAt ?? "") - Date.parse(first.latestAt ?? ""));
185
+ const retained = [
186
+ ...Map.groupBy(stopped, (status) => status["workflowId"]).values(),
187
+ ].flatMap((group) => group.slice(0, 20));
188
+ return new Set(retained.flatMap((status) => {
191
189
  const issue = establishedIssue(context.repository, status["issueIdentity"]);
192
190
  return status.state === "completed" && issue !== null
193
191
  ? [JSON.stringify(issue)]
@@ -213,9 +211,7 @@ async function service(reader, now) {
213
211
  const issues = stoppedIssues(runs, { now, repository: reader.repository });
214
212
  const older = issues.size === 0
215
213
  ? []
216
- : summaries.filter((summary) => !recentRun(summary.latestAt, now) &&
217
- summary["workflowId"] === "develop-issue" &&
218
- summary.state === "completed");
214
+ : summaries.filter((summary) => !recentRun(summary.latestAt, now) && summary.state === "completed");
219
215
  for (const summary of older) {
220
216
  const candidate = await completionOf(reader, { summary, issues });
221
217
  if (candidate !== null)
@@ -223,6 +219,9 @@ async function service(reader, now) {
223
219
  }
224
220
  return {
225
221
  service: "up",
222
+ ...("schedules" in status
223
+ ? { schedules: shownSchedules(status["schedules"]) }
224
+ : {}),
226
225
  ...(record(status["queue"]) === undefined
227
226
  ? {}
228
227
  : { queue: record(status["queue"]) ?? {} }),
@@ -247,11 +246,13 @@ async function singleRun(reader, runId) {
247
246
  const status = statusOf(reply.result["run"]);
248
247
  const known = reader.details.get(runId);
249
248
  const value = known !== undefined && known.sequence === status.latestSequence
250
- ? refreshedLinks(known.value, status)
249
+ ? refreshedStatus(known.value, status)
251
250
  : await remember(reader, status);
251
+ const metadata = await asked(reader, { operation: "status", parameters: {} });
252
252
  return {
253
253
  ...unavailable,
254
254
  service: "up",
255
+ workflowStages: stageMetadata(metadata["workflowStages"]),
255
256
  runs: [value],
256
257
  decisions: await decisionsOf(reader),
257
258
  };
@@ -1,3 +1,5 @@
1
+ import type { ShownSchedule, ShownSchedules } from "./schedule-render.ts";
2
+ import type { CommandWaitView } from "../authority/command-wait-view.ts";
1
3
  import type { CommandEffect } from "../command-settings.ts";
2
4
  import type { CardForm } from "./watch-run-input.ts";
3
5
  import type { CiRerunNotice } from "./github-ci-progress.ts";
@@ -68,6 +70,12 @@ export interface WatchStep {
68
70
  readonly startedAt: string | null;
69
71
  }
70
72
  export interface WatchCard {
73
+ readonly scheduled?: {
74
+ readonly slot: number;
75
+ };
76
+ readonly commandWaits?: readonly CommandWaitView[];
77
+ readonly spent?: number | null;
78
+ readonly limit?: number | null;
71
79
  readonly awaitedRunId?: string | null;
72
80
  readonly parentRunId?: string | null;
73
81
  readonly chainId?: string;
@@ -95,6 +103,7 @@ export interface WatchColumn {
95
103
  readonly cards: readonly WatchCard[];
96
104
  }
97
105
  export interface WatchWorkflow {
106
+ readonly schedule?: ShownSchedule;
98
107
  readonly workflowId: string;
99
108
  readonly activeCount: number;
100
109
  readonly waitingCount: number;
@@ -102,6 +111,10 @@ export interface WatchWorkflow {
102
111
  readonly columns: readonly WatchColumn[];
103
112
  }
104
113
  export interface WatchRun {
114
+ readonly scheduled?: {
115
+ readonly slot: number;
116
+ };
117
+ readonly commandWaits?: readonly CommandWaitView[];
105
118
  readonly awaitedRunId?: string | null;
106
119
  readonly commandEffects?: readonly CommandEffect[];
107
120
  readonly ciReruns?: readonly CiRerunNotice[];
@@ -140,6 +153,7 @@ export interface WatchRun {
140
153
  readonly mergeCommit: string | null;
141
154
  }
142
155
  export interface WatchModel {
156
+ readonly schedules?: ShownSchedules;
143
157
  readonly queue?: Fields;
144
158
  readonly workflowRefusals?: readonly WorkflowRefusal[];
145
159
  readonly cardForms?: readonly CardForm[];
@@ -10,5 +10,4 @@ export interface CardForm {
10
10
  readonly source: "ticket" | "prompt" | "input";
11
11
  readonly fields: readonly CardField[];
12
12
  }
13
- export declare function builtinCardForms(): readonly CardForm[];
14
13
  export declare function cardRunRequest(forms: readonly CardForm[], value: unknown): ServiceRequest | null;
@@ -1,17 +1,4 @@
1
1
  import { record } from "../protocol.js";
2
- export function builtinCardForms() {
3
- const entries = [
4
- ["develop-issue", "ticket", "Issue number"],
5
- ["develop-ticket", "ticket", "Ticket ID"],
6
- ["develop-prd", "prompt", "Idea"],
7
- ["develop-prompt", "prompt", "Task"],
8
- ];
9
- return entries.map(([workflowId, name, label]) => ({
10
- workflowId,
11
- source: name === "ticket" ? "ticket" : "prompt",
12
- fields: [{ name, label, multiline: name === "prompt", optional: false }],
13
- }));
14
- }
15
2
  function validFields(form, fields) {
16
3
  if (Object.keys(fields).some((name) => !form.fields.some((field) => field.name === name)))
17
4
  return false;
@@ -9,7 +9,7 @@ import { dirname, join } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { watchReader } from "./watch-reads.js";
11
11
  import { ciRerunNotices } from "./github-ci-progress.js";
12
- import { turnOf, watchModel } from "./watch-model.js";
12
+ import { turnActivity, turnOf, watchModel, } from "./watch-model.js";
13
13
  import { pageDocument } from "./watch-page.js";
14
14
  import { watchStream } from "./watch-stream.js";
15
15
  export const defaultWatchPort = 3663;
@@ -39,7 +39,7 @@ function parsed(text) {
39
39
  return null;
40
40
  }
41
41
  }
42
- const noStream = { size: 0, lines: [], activeAt: null };
42
+ const noStream = { size: 0, activity: [], activeAt: null };
43
43
  async function streamed(watch, file) {
44
44
  const found = await stat(file).catch(() => null);
45
45
  if (found === null)
@@ -48,9 +48,9 @@ async function streamed(watch, file) {
48
48
  const activeAt = found.mtime.toISOString();
49
49
  if (known?.size === found.size)
50
50
  return { ...known, activeAt };
51
- const lines = (await readFile(file, "utf8").catch(() => "")).split("\n");
52
- watch.streams.set(file, { size: found.size, lines });
53
- return { size: found.size, lines, activeAt };
51
+ const activity = turnActivity((await readFile(file, "utf8").catch(() => "")).split("\n"));
52
+ watch.streams.set(file, { size: found.size, activity });
53
+ return { size: found.size, activity, activeAt };
54
54
  }
55
55
  async function stored(file) {
56
56
  return parsed(await readFile(file, "utf8").catch(() => "null"));
@@ -67,7 +67,7 @@ async function turns(watch) {
67
67
  key,
68
68
  started,
69
69
  result: await stored(join(journal, key, "result.json")),
70
- lines: stream.lines,
70
+ activity: stream.activity,
71
71
  activeAt: stream.activeAt,
72
72
  }));
73
73
  }
@@ -1,8 +1,6 @@
1
1
  import type { CardControls } from "./watch-card-view.ts";
2
2
  import type { PhoneControls } from "./watch-phone-view.ts";
3
- import { type StartControls } from "./watch-header-view.ts";
4
3
  export { clove, Header } from "./watch-header-view.ts";
5
- export type { StartControls } from "./watch-header-view.ts";
6
4
  import { type Shown } from "./watch-element.ts";
7
5
  import { type Copying, type WatchModel, type WatchPhase, type WatchRun } from "./watch-render.ts";
8
6
  import { type ColumnControls } from "./watch-board-view.ts";
@@ -33,7 +31,6 @@ export interface PageProps {
33
31
  readonly stale: boolean;
34
32
  readonly copying: Copying;
35
33
  readonly columns?: ColumnControls;
36
- readonly start?: StartControls;
37
34
  readonly phone?: PhoneControls | undefined;
38
35
  }
39
36
  export declare function RefusedWorkflows(props: {
@@ -1,3 +1,5 @@
1
+ import { scheduledText } from "./schedule-render.js";
2
+ import { CommandWaits, effectBudgetText } from "./command-waits.js";
1
3
  import { Header, Queue } from "./watch-header-view.js";
2
4
  export { clove, Header } from "./watch-header-view.js";
3
5
  import { priceText, usageText, totalsSuffix } from "./usage-render.js";
@@ -8,7 +10,6 @@ import { boardWorkflow, watchHash, watchLocation } from "./watch-navigation.js";
8
10
  import { Decision, Events, Turn, PullRequest } from "./watch-turn.js";
9
11
  import { Commands } from "./command-effects.js";
10
12
  export { PullRequest } from "./watch-turn.js";
11
- import { StartRun } from "./watch-start-view.js";
12
13
  export function BannerDetail(props) {
13
14
  const { run, now } = props;
14
15
  const turn = run.turns.at(-1);
@@ -88,9 +89,7 @@ export function Review(props) {
88
89
  export function Run(props) {
89
90
  const { run, now, copying } = props;
90
91
  const turn = run.turns.at(-1);
91
- const budget = run.spent === null || run.limit === null
92
- ? ""
93
- : ` · ${String(run.spent)} of ${String(run.limit)} effect units`;
92
+ const budget = effectBudgetText(run.spent, run.limit);
94
93
  const none = "No agent turn for this run.";
95
94
  const turns = run.turns.length === 0
96
95
  ? element("p", { class: "meta", children: none })
@@ -103,13 +102,17 @@ export function Run(props) {
103
102
  }),
104
103
  ]);
105
104
  const foot = [
106
- `${run.workflowId} · ${run.runId}${budget}`,
105
+ `${run.workflowId} · ${run.runId}${budget === "" ? "" : ` · ${budget}`}`,
107
106
  element("br", {}),
108
107
  `${run.code}: ${run.detail}`,
109
108
  ];
110
109
  return [
111
110
  component(Banner, { run, now }),
111
+ run.scheduled === undefined
112
+ ? null
113
+ : element("p", { class: "meta", children: scheduledText(run.scheduled) }),
112
114
  component(HandoffLinks, { run }),
115
+ component(CommandWaits, { waits: run.commandWaits ?? [] }),
113
116
  element("section", {
114
117
  class: "run-usage",
115
118
  children: [
@@ -181,21 +184,18 @@ export function Page(props) {
181
184
  return component(BoardPage, { ...props, model, workflowId, down });
182
185
  }
183
186
  export function BoardPage(props) {
184
- const { model, workflowId, down, now, stale, copying } = props;
187
+ const { model, workflowId, down, now, stale } = props;
185
188
  const workflow = model.workflows.find((item) => item.workflowId === workflowId);
186
- const start = props.start ?? { shown: false, onToggle: () => undefined };
187
189
  return element("div", {
188
190
  class: "board-page",
189
191
  children: [
190
- component(Header, { model, stale, start, phone: props.phone, now }),
192
+ component(Header, { model, stale, phone: props.phone, now }),
191
193
  component(Queue, { model }),
192
194
  down,
195
+ model.schedules !== undefined && "status" in model.schedules
196
+ ? element("p", { class: "meta", children: "Schedules: unavailable" })
197
+ : null,
193
198
  component(RefusedWorkflows, { model }),
194
- component(StartRun, {
195
- workflows: model.workflows,
196
- copying,
197
- shown: start.shown,
198
- }),
199
199
  component(WorkflowList, {
200
200
  workflows: model.workflows,
201
201
  selected: workflowId,
package/dist/src/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createProbeRunner } from "./cli/probe-command.js";
3
+ import { terminateHarness } from "./cli/harness-processes.js";
3
4
  import { settingsEnvironment } from "./cli/project-settings.js";
5
+ import { constants as systemConstants } from "node:os";
4
6
  import { addAbortListener } from "node:events";
5
7
  import { execFile, execFileSync, spawn } from "node:child_process";
6
8
  import { existsSync, realpathSync } from "node:fs";
@@ -30,7 +32,7 @@ const watchCommand = async (request) => {
30
32
  cwd: request.directory,
31
33
  signal: request.signal,
32
34
  killSignal: "SIGKILL",
33
- maxBuffer: 1024 * 1024,
35
+ maxBuffer: request.outputBytes ?? 1024 * 1024,
34
36
  encoding: "utf8",
35
37
  }, (error, stdout) => {
36
38
  const code = error === null ? 0 : error.code;
@@ -58,6 +60,45 @@ const watchCommand = async (request) => {
58
60
  listener[Symbol.dispose]();
59
61
  }
60
62
  };
63
+ function startHarness(request) {
64
+ return new Promise((settle) => {
65
+ try {
66
+ const child = spawn(request.executable, request.arguments, {
67
+ cwd: request.directory,
68
+ env: request.environment,
69
+ stdio: "inherit",
70
+ });
71
+ const interrupt = () => undefined;
72
+ const terminate = () => {
73
+ terminateHarness(child.pid, {
74
+ snapshot: () => execFileSync("/bin/ps", ["-axo", "pid=,ppid="], {
75
+ encoding: "utf8",
76
+ timeout: 1000,
77
+ }),
78
+ kill: (identifier, signal) => process.kill(identifier, signal),
79
+ });
80
+ };
81
+ const cleanup = () => {
82
+ process.off("SIGINT", interrupt);
83
+ process.off("SIGTERM", terminate);
84
+ };
85
+ process.on("SIGINT", interrupt);
86
+ process.on("SIGTERM", terminate);
87
+ child.once("error", () => {
88
+ cleanup();
89
+ settle(null);
90
+ });
91
+ child.once("close", (status, signal) => {
92
+ cleanup();
93
+ settle(status ??
94
+ (signal === null ? 1 : 128 + systemConstants.signals[signal]));
95
+ });
96
+ }
97
+ catch {
98
+ settle(null);
99
+ }
100
+ });
101
+ }
61
102
  function startArguments(input) {
62
103
  const { runtime, repository, argumentsList } = input;
63
104
  const located = [
@@ -82,7 +123,10 @@ function startArguments(input) {
82
123
  function toolProcess(input) {
83
124
  const program = join(import.meta.dirname, "..", "tools", `${input.tool}${extname(import.meta.filename)}`);
84
125
  const child = spawn(process.execPath, [program, ...input.argumentsList], {
85
- stdio: "inherit",
126
+ detached: input.tool === "red-green-command",
127
+ stdio: input.tool === "red-green-command"
128
+ ? ["inherit", "inherit", "inherit", "ipc"]
129
+ : "inherit",
86
130
  env: {
87
131
  ...process.env,
88
132
  PATH: [dirname(process.execPath), process.env["PATH"]].join(delimiter),
@@ -101,9 +145,13 @@ function toolProcess(input) {
101
145
  process.on("SIGTERM", terminate);
102
146
  return new Promise((settle) => {
103
147
  child.once("close", (status) => {
104
- process.off("SIGINT", interrupt);
105
- process.off("SIGTERM", terminate);
106
- settle(stopping && status === 75 ? 0 : (status ?? 1));
148
+ setImmediate(() => {
149
+ setImmediate(() => {
150
+ process.off("SIGINT", interrupt);
151
+ process.off("SIGTERM", terminate);
152
+ settle(stopping && status === 75 ? 0 : (status ?? 1));
153
+ });
154
+ });
107
155
  });
108
156
  });
109
157
  }
@@ -160,6 +208,11 @@ else if (command === "templates")
160
208
  tool: "template-command",
161
209
  argumentsList: options,
162
210
  });
211
+ else if (command === "red-green")
212
+ process.exitCode = await toolProcess({
213
+ tool: "red-green-command",
214
+ argumentsList: options,
215
+ });
163
216
  else if (command === "start")
164
217
  process.exitCode = await start();
165
218
  else {
@@ -174,6 +227,7 @@ else {
174
227
  await main(process.argv.slice(2), {
175
228
  repository,
176
229
  environment,
230
+ startHarness,
177
231
  doctorProbe: createProbeRunner({
178
232
  spawn: (input) => spawn(input.executable, input.arguments, input.options),
179
233
  kill: (identifier, signal) => process.kill(identifier, signal),
@@ -0,0 +1,4 @@
1
+ import type { HandoffValue, CommandWaitRequest } from "./step-bridge.ts";
2
+ export type { CommandWaitRequest, CommandWaitOutcome, CommandWaitProgress, } from "./step-bridge.ts";
3
+ export declare function isCommandWaitRequest(value: unknown): value is CommandWaitRequest;
4
+ export declare function commandWaitValue(value: unknown, ancestors?: ReadonlySet<unknown>): value is HandoffValue;
@@ -0,0 +1,45 @@
1
+ export function isCommandWaitRequest(value) {
2
+ if (!dataFields(value, ["name", "input"]))
3
+ return false;
4
+ const name = Reflect.get(value, "name");
5
+ return (typeof name === "string" &&
6
+ name.length > 0 &&
7
+ commandWaitValue(Reflect.get(value, "input")));
8
+ }
9
+ function dataFields(value, keys) {
10
+ if (value === null || typeof value !== "object" || Array.isArray(value))
11
+ return false;
12
+ return (Reflect.ownKeys(value).length === keys.length &&
13
+ keys.every((key) => {
14
+ const field = Object.getOwnPropertyDescriptor(value, key);
15
+ return field?.enumerable === true && Object.hasOwn(field, "value");
16
+ }));
17
+ }
18
+ export function commandWaitValue(value, ancestors = new Set()) {
19
+ if (value === null || typeof value === "string" || typeof value === "boolean")
20
+ return true;
21
+ if (typeof value === "number")
22
+ return Number.isFinite(value);
23
+ if (typeof value !== "object" || ancestors.has(value))
24
+ return false;
25
+ return commandWaitObject(value, ancestors);
26
+ }
27
+ function commandWaitObject(value, ancestors) {
28
+ const prototype = Object.getPrototypeOf(value);
29
+ const expectedPrototype = Object.getPrototypeOf(Array.isArray(value) ? [] : {});
30
+ if (prototype !== expectedPrototype && prototype !== null)
31
+ return false;
32
+ const keys = Array.isArray(value)
33
+ ? Array.from({ length: value.length }, (_, index) => String(index))
34
+ : Object.keys(value);
35
+ const ownKeys = Reflect.ownKeys(value).filter((key) => !Array.isArray(value) || key !== "length");
36
+ if (keys.length !== ownKeys.length)
37
+ return false;
38
+ const seen = new Set([...ancestors, value]);
39
+ return keys.every((key) => {
40
+ const field = Object.getOwnPropertyDescriptor(value, key);
41
+ return (field?.enumerable === true &&
42
+ Object.hasOwn(field, "value") &&
43
+ commandWaitValue(field.value, seen));
44
+ });
45
+ }