cloving 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (484) hide show
  1. package/README.md +329 -215
  2. package/dist/src/agent-harness-shell.d.ts +2 -1
  3. package/dist/src/agent-harness-shell.js +8 -5
  4. package/dist/src/authority/command-wait-checks.d.ts +8 -0
  5. package/dist/src/authority/command-wait-checks.js +88 -0
  6. package/dist/src/authority/command-wait-results.d.ts +15 -0
  7. package/dist/src/authority/command-wait-results.js +73 -0
  8. package/dist/src/authority/command-wait-view.d.ts +21 -0
  9. package/dist/src/authority/command-wait-view.js +34 -0
  10. package/dist/src/authority/command-waits.d.ts +39 -0
  11. package/dist/src/authority/command-waits.js +97 -0
  12. package/dist/src/authority/commands.js +22 -1
  13. package/dist/src/authority/dispatch-decision.d.ts +8 -0
  14. package/dist/src/authority/dispatch-decision.js +26 -1
  15. package/dist/src/authority/effect-requests.js +6 -3
  16. package/dist/src/authority/effect-results.js +4 -2
  17. package/dist/src/authority/history-state.d.ts +2 -0
  18. package/dist/src/authority/history-state.js +13 -1
  19. package/dist/src/authority/instants.d.ts +1 -0
  20. package/dist/src/authority/instants.js +21 -0
  21. package/dist/src/authority/reduction-state.d.ts +2 -0
  22. package/dist/src/authority/reduction-state.js +5 -0
  23. package/dist/src/authority/reduction.js +24 -0
  24. package/dist/src/authority/status-input.d.ts +2 -0
  25. package/dist/src/authority/status.d.ts +2 -0
  26. package/dist/src/authority/status.js +1 -0
  27. package/dist/src/authority/stop-sequence.d.ts +1 -0
  28. package/dist/src/authority/stop-sequence.js +2 -1
  29. package/dist/src/authority/transitions.d.ts +6 -4
  30. package/dist/src/authority/transitions.js +7 -0
  31. package/dist/src/broker/actor/authority-health.d.ts +9 -0
  32. package/dist/src/broker/actor/authority-health.js +24 -0
  33. package/dist/src/broker/actor/authority-queries.d.ts +2 -1
  34. package/dist/src/broker/actor/authority-queries.js +14 -5
  35. package/dist/src/broker/actor/authority-receipts.js +1 -1
  36. package/dist/src/broker/actor/authority-runtime.d.ts +4 -2
  37. package/dist/src/broker/actor/authority-runtime.js +29 -20
  38. package/dist/src/broker/actor/authority-session.d.ts +6 -19
  39. package/dist/src/broker/actor/authority-session.js +66 -88
  40. package/dist/src/broker/actor/authority-settings.d.ts +2 -1
  41. package/dist/src/broker/actor/authority-settings.js +6 -3
  42. package/dist/src/broker/actor/authority-upgrades.d.ts +23 -2
  43. package/dist/src/broker/actor/authority-upgrades.js +8 -2
  44. package/dist/src/broker/actor/authority-watchdog.d.ts +6 -2
  45. package/dist/src/broker/actor/authority-watchdog.js +31 -9
  46. package/dist/src/broker/actor/authority.js +3 -1
  47. package/dist/src/broker/actor/delivery-intents.js +2 -0
  48. package/dist/src/broker/actor/effect-delivery.d.ts +1 -1
  49. package/dist/src/broker/actor/effect-delivery.js +15 -2
  50. package/dist/src/broker/actor/effect-plan.js +11 -2
  51. package/dist/src/broker/actor/effect-queries.d.ts +4 -0
  52. package/dist/src/broker/actor/effect-queries.js +9 -1
  53. package/dist/src/broker/actor/effect-turn.js +2 -0
  54. package/dist/src/broker/actor/projection-memory.d.ts +43 -0
  55. package/dist/src/broker/actor/projection-memory.js +207 -0
  56. package/dist/src/broker/actor/runtime-parts.d.ts +5 -0
  57. package/dist/src/broker/actor/runtime-parts.js +8 -3
  58. package/dist/src/broker/actor/stop-plan.js +9 -3
  59. package/dist/src/broker/actor/stop-turn.d.ts +2 -0
  60. package/dist/src/broker/actor/stop-turn.js +24 -11
  61. package/dist/src/broker/actor/turn-draft.d.ts +1 -0
  62. package/dist/src/broker/actor/turn-draft.js +4 -0
  63. package/dist/src/broker/actor/turn-state.js +5 -1
  64. package/dist/src/broker/authority-admission.js +60 -0
  65. package/dist/src/broker/authority-restore.d.ts +2 -0
  66. package/dist/src/broker/authority-restore.js +35 -1
  67. package/dist/src/broker/command-wait-restore.d.ts +31 -0
  68. package/dist/src/broker/command-wait-restore.js +86 -0
  69. package/dist/src/broker/decision-restore.d.ts +1 -1
  70. package/dist/src/broker/decision-restore.js +1 -0
  71. package/dist/src/broker/effect-upgrade.d.ts +3 -0
  72. package/dist/src/broker/effect-upgrade.js +11 -2
  73. package/dist/src/cli/arguments.d.ts +13 -0
  74. package/dist/src/cli/arguments.js +43 -3
  75. package/dist/src/cli/chat-stores.d.ts +2 -0
  76. package/dist/src/cli/chat-stores.js +113 -0
  77. package/dist/src/cli/client.d.ts +7 -2
  78. package/dist/src/cli/client.js +13 -7
  79. package/dist/src/cli/command-requests.d.ts +46 -0
  80. package/dist/src/cli/command-requests.js +84 -0
  81. package/dist/src/cli/command-waits.d.ts +9 -0
  82. package/dist/src/cli/command-waits.js +51 -0
  83. package/dist/src/cli/commands.js +70 -88
  84. package/dist/src/cli/config-file.d.ts +2 -2
  85. package/dist/src/cli/config-file.js +4 -2
  86. package/dist/src/cli/config-notice.js +5 -2
  87. package/dist/src/cli/doctor-retention.d.ts +2 -0
  88. package/dist/src/cli/doctor-retention.js +93 -0
  89. package/dist/src/cli/doctor-workflows.d.ts +5 -2
  90. package/dist/src/cli/doctor-workflows.js +12 -7
  91. package/dist/src/cli/doctor.js +11 -9
  92. package/dist/src/cli/harness-probes.d.ts +2 -0
  93. package/dist/src/cli/harness-probes.js +1 -1
  94. package/dist/src/cli/harness-processes.d.ts +6 -0
  95. package/dist/src/cli/harness-processes.js +36 -0
  96. package/dist/src/cli/init-command.d.ts +9 -0
  97. package/dist/src/cli/init-command.js +52 -0
  98. package/dist/src/cli/init-prompt-command.d.ts +10 -0
  99. package/dist/src/cli/init-prompt-command.js +145 -0
  100. package/dist/src/cli/init-prompt.d.ts +35 -0
  101. package/dist/src/cli/init-prompt.js +74 -0
  102. package/dist/src/cli/init-selection.d.ts +13 -0
  103. package/dist/src/cli/init-selection.js +75 -0
  104. package/dist/src/cli/main.d.ts +2 -0
  105. package/dist/src/cli/main.js +37 -7
  106. package/dist/src/cli/mcp-server.d.ts +1 -0
  107. package/dist/src/cli/mcp-server.js +156 -0
  108. package/dist/src/cli/mcp-tools.d.ts +15 -0
  109. package/dist/src/cli/mcp-tools.js +242 -0
  110. package/dist/src/cli/queue-arguments.d.ts +15 -0
  111. package/dist/src/cli/queue-arguments.js +31 -0
  112. package/dist/src/cli/queue-render.d.ts +3 -0
  113. package/dist/src/cli/queue-render.js +26 -0
  114. package/dist/src/cli/reload-render.d.ts +2 -0
  115. package/dist/src/cli/reload-render.js +12 -0
  116. package/dist/src/cli/render.d.ts +1 -1
  117. package/dist/src/cli/render.js +54 -38
  118. package/dist/src/cli/report-ci-jobs.d.ts +23 -0
  119. package/dist/src/cli/report-ci-jobs.js +103 -0
  120. package/dist/src/cli/report-ci-usage.d.ts +9 -0
  121. package/dist/src/cli/report-ci-usage.js +185 -0
  122. package/dist/src/cli/report-ci.d.ts +13 -0
  123. package/dist/src/cli/report-ci.js +74 -0
  124. package/dist/src/cli/report-closures.d.ts +6 -0
  125. package/dist/src/cli/report-closures.js +97 -0
  126. package/dist/src/cli/report-flow.d.ts +7 -0
  127. package/dist/src/cli/report-flow.js +122 -0
  128. package/dist/src/cli/report-github-reader.d.ts +29 -0
  129. package/dist/src/cli/report-github-reader.js +157 -0
  130. package/dist/src/cli/report-github.d.ts +15 -8
  131. package/dist/src/cli/report-github.js +131 -150
  132. package/dist/src/cli/report-operator.d.ts +3 -0
  133. package/dist/src/cli/report-operator.js +40 -0
  134. package/dist/src/cli/report-ready.d.ts +3 -0
  135. package/dist/src/cli/report-ready.js +96 -0
  136. package/dist/src/cli/report-text.d.ts +9 -0
  137. package/dist/src/cli/report-text.js +33 -0
  138. package/dist/src/cli/report.d.ts +1 -0
  139. package/dist/src/cli/report.js +104 -27
  140. package/dist/src/cli/runtime-arguments.d.ts +18 -3
  141. package/dist/src/cli/runtime-arguments.js +29 -20
  142. package/dist/src/cli/schedule-arguments.d.ts +14 -0
  143. package/dist/src/cli/schedule-arguments.js +26 -0
  144. package/dist/src/cli/schedule-render.d.ts +22 -0
  145. package/dist/src/cli/schedule-render.js +73 -0
  146. package/dist/src/cli/terminal-text.d.ts +3 -0
  147. package/dist/src/cli/terminal-text.js +28 -0
  148. package/dist/src/cli/toolchain.d.ts +1 -0
  149. package/dist/src/cli/toolchain.js +1 -0
  150. package/dist/src/cli/usage-render.d.ts +1 -0
  151. package/dist/src/cli/usage-render.js +28 -1
  152. package/dist/src/cli/usage.d.ts +2 -2
  153. package/dist/src/cli/usage.js +89 -11
  154. package/dist/src/cli/watch-actions.js +2 -2
  155. package/dist/src/cli/watch-api-document.d.ts +430 -0
  156. package/dist/src/cli/watch-api-document.js +291 -0
  157. package/dist/src/cli/watch-api-run.d.ts +56 -0
  158. package/dist/src/cli/watch-api-run.js +112 -0
  159. package/dist/src/cli/watch-api.d.ts +17 -0
  160. package/dist/src/cli/watch-api.js +230 -0
  161. package/dist/src/cli/watch-board-view.js +19 -2
  162. package/dist/src/cli/watch-board.d.ts +4 -2
  163. package/dist/src/cli/watch-board.js +53 -43
  164. package/dist/src/cli/watch-card-forms.d.ts +1 -1
  165. package/dist/src/cli/watch-card-forms.js +11 -10
  166. package/dist/src/cli/watch-client.d.ts +1 -1
  167. package/dist/src/cli/watch-client.js +2 -11
  168. package/dist/src/cli/watch-header-view.d.ts +1 -5
  169. package/dist/src/cli/watch-header-view.js +17 -21
  170. package/dist/src/cli/watch-model.d.ts +5 -1
  171. package/dist/src/cli/watch-model.js +20 -11
  172. package/dist/src/cli/watch-page.js +15 -15
  173. package/dist/src/cli/watch-phases.d.ts +12 -3
  174. package/dist/src/cli/watch-phases.js +68 -11
  175. package/dist/src/cli/watch-pr-board.js +1 -1
  176. package/dist/src/cli/watch-pr-command.d.ts +1 -0
  177. package/dist/src/cli/watch-pull-request.js +18 -8
  178. package/dist/src/cli/watch-reads.d.ts +3 -0
  179. package/dist/src/cli/watch-reads.js +22 -20
  180. package/dist/src/cli/watch-render.d.ts +15 -0
  181. package/dist/src/cli/watch-run-input.d.ts +0 -1
  182. package/dist/src/cli/watch-run-input.js +0 -13
  183. package/dist/src/cli/watch-server.js +24 -10
  184. package/dist/src/cli/watch-view.d.ts +0 -3
  185. package/dist/src/cli/watch-view.js +13 -13
  186. package/dist/src/cli.js +73 -12
  187. package/dist/src/command-wait.d.ts +4 -0
  188. package/dist/src/command-wait.js +45 -0
  189. package/dist/src/configuration-change.d.ts +7 -0
  190. package/dist/src/configuration-change.js +14 -0
  191. package/dist/src/configuration-display.js +2 -0
  192. package/dist/src/configuration-schema.d.ts +14 -0
  193. package/dist/src/configuration-schema.js +6 -2
  194. package/dist/src/daemon/attachment-file.d.ts +6 -0
  195. package/dist/src/daemon/attachment-file.js +86 -0
  196. package/dist/src/daemon/attachments.d.ts +14 -0
  197. package/dist/src/daemon/attachments.js +17 -0
  198. package/dist/src/daemon/claude-recovery.d.ts +50 -0
  199. package/dist/src/daemon/claude-recovery.js +208 -0
  200. package/dist/src/daemon/claude-stream.d.ts +27 -0
  201. package/dist/src/daemon/claude-stream.js +63 -0
  202. package/dist/src/daemon/claude-turn.d.ts +11 -25
  203. package/dist/src/daemon/claude-turn.js +64 -81
  204. package/dist/src/daemon/claude-worker.d.ts +5 -4
  205. package/dist/src/daemon/claude-worker.js +127 -126
  206. package/dist/src/daemon/daemon-adapters.d.ts +9 -0
  207. package/dist/src/daemon/daemon-adapters.js +33 -5
  208. package/dist/src/daemon/daemon-loops.d.ts +5 -1
  209. package/dist/src/daemon/daemon-loops.js +18 -10
  210. package/dist/src/daemon/daemon.js +80 -61
  211. package/dist/src/daemon/github-attach.d.ts +2 -0
  212. package/dist/src/daemon/github-attach.js +222 -0
  213. package/dist/src/daemon/github-calls.js +1 -1
  214. package/dist/src/daemon/github-ci-filters.d.ts +6 -0
  215. package/dist/src/daemon/github-ci-filters.js +138 -0
  216. package/dist/src/daemon/github-ci-workflows.d.ts +10 -0
  217. package/dist/src/daemon/github-ci-workflows.js +148 -0
  218. package/dist/src/daemon/github-ci.js +22 -4
  219. package/dist/src/daemon/github-issue-methods.d.ts +2 -0
  220. package/dist/src/daemon/github-issue-methods.js +37 -2
  221. package/dist/src/daemon/github-merge-call.d.ts +2 -1
  222. package/dist/src/daemon/github-merge-call.js +10 -3
  223. package/dist/src/daemon/github-merge.js +52 -24
  224. package/dist/src/daemon/github-methods.js +18 -4
  225. package/dist/src/daemon/github-operation.d.ts +4 -0
  226. package/dist/src/daemon/github-operation.js +6 -0
  227. package/dist/src/daemon/github-worker.d.ts +4 -0
  228. package/dist/src/daemon/github-worker.js +6 -0
  229. package/dist/src/daemon/issue-queue.d.ts +4 -1
  230. package/dist/src/daemon/issue-queue.js +76 -78
  231. package/dist/src/daemon/merge-turns.d.ts +10 -0
  232. package/dist/src/daemon/merge-turns.js +37 -0
  233. package/dist/src/daemon/notify-events.d.ts +6 -0
  234. package/dist/src/daemon/notify-events.js +84 -0
  235. package/dist/src/daemon/notify-journal.js +1 -1
  236. package/dist/src/daemon/notify-message.d.ts +4 -1
  237. package/dist/src/daemon/notify-message.js +36 -0
  238. package/dist/src/daemon/notify.d.ts +1 -1
  239. package/dist/src/daemon/notify.js +5 -77
  240. package/dist/src/daemon/operation-journal.d.ts +1 -0
  241. package/dist/src/daemon/queue-control.d.ts +13 -0
  242. package/dist/src/daemon/queue-control.js +86 -0
  243. package/dist/src/daemon/queue-journal.d.ts +20 -0
  244. package/dist/src/daemon/queue-journal.js +89 -0
  245. package/dist/src/daemon/queue-status.d.ts +9 -3
  246. package/dist/src/daemon/queue-status.js +17 -4
  247. package/dist/src/daemon/record-archive.d.ts +19 -0
  248. package/dist/src/daemon/record-archive.js +82 -0
  249. package/dist/src/daemon/record-sweep.d.ts +24 -0
  250. package/dist/src/daemon/record-sweep.js +147 -0
  251. package/dist/src/daemon/resume-index-lock.d.ts +13 -0
  252. package/dist/src/daemon/resume-index-lock.js +160 -0
  253. package/dist/src/daemon/resume-worktree.d.ts +7 -0
  254. package/dist/src/daemon/resume-worktree.js +29 -0
  255. package/dist/src/daemon/retrying-turn.d.ts +2 -12
  256. package/dist/src/daemon/retrying-turn.js +98 -172
  257. package/dist/src/daemon/schedule-control.d.ts +26 -0
  258. package/dist/src/daemon/schedule-control.js +110 -0
  259. package/dist/src/daemon/schedule-journal.d.ts +2 -15
  260. package/dist/src/daemon/schedule-journal.js +67 -4
  261. package/dist/src/daemon/settings-reload.d.ts +26 -0
  262. package/dist/src/daemon/settings-reload.js +82 -0
  263. package/dist/src/daemon/ticket-registration.d.ts +2 -7
  264. package/dist/src/daemon/ticket-registration.js +15 -81
  265. package/dist/src/daemon/turn-attempt-journal.d.ts +38 -0
  266. package/dist/src/daemon/turn-attempt-journal.js +136 -0
  267. package/dist/src/daemon/turn-attempts.d.ts +47 -0
  268. package/dist/src/daemon/turn-attempts.js +166 -0
  269. package/dist/src/daemon/turn-details.d.ts +4 -0
  270. package/dist/src/daemon/turn-details.js +33 -0
  271. package/dist/src/daemon/turn-report.d.ts +8 -1
  272. package/dist/src/daemon/turn-report.js +39 -0
  273. package/dist/src/daemon/work-tree.d.ts +9 -0
  274. package/dist/src/daemon/work-tree.js +20 -0
  275. package/dist/src/daemon/worker-profile.d.ts +9 -0
  276. package/dist/src/daemon/worker-profile.js +44 -0
  277. package/dist/src/daemon/workflow-generations.d.ts +7 -4
  278. package/dist/src/daemon/workflow-generations.js +22 -14
  279. package/dist/src/daemon/workflow-reload.d.ts +41 -0
  280. package/dist/src/daemon/workflow-reload.js +244 -0
  281. package/dist/src/daemon/workflow-replay.d.ts +2 -1
  282. package/dist/src/daemon/workflow-replay.js +7 -3
  283. package/dist/src/daemon/workflow-schedules.d.ts +3 -0
  284. package/dist/src/daemon/workflow-schedules.js +98 -45
  285. package/dist/src/daemon/worktree-processes.d.ts +9 -0
  286. package/dist/src/daemon/worktree-processes.js +151 -0
  287. package/dist/src/doctor-report.d.ts +4 -6
  288. package/dist/src/doctor-report.js +8 -13
  289. package/dist/src/harness-start.d.ts +15 -0
  290. package/dist/src/harness-start.js +37 -0
  291. package/dist/src/host/command-wait.d.ts +38 -0
  292. package/dist/src/host/command-wait.js +115 -0
  293. package/dist/src/host/start-delivery.d.ts +5 -7
  294. package/dist/src/host/start-delivery.js +3 -15
  295. package/dist/src/host/step-bridge-host.d.ts +2 -0
  296. package/dist/src/host/step-bridge-host.js +21 -0
  297. package/dist/src/operation-identity.d.ts +1 -0
  298. package/dist/src/operation-identity.js +4 -0
  299. package/dist/src/private-config.schema.json +71 -0
  300. package/dist/src/project-config.schema.json +40 -0
  301. package/dist/src/project-settings.d.ts +16 -2
  302. package/dist/src/project-settings.js +35 -1
  303. package/dist/src/queue-spend.d.ts +4 -0
  304. package/dist/src/queue-spend.js +21 -0
  305. package/dist/src/queue-state.d.ts +26 -0
  306. package/dist/src/queue-state.js +34 -0
  307. package/dist/src/record-retention.d.ts +46 -0
  308. package/dist/src/record-retention.js +81 -0
  309. package/dist/src/relay/relay-service.d.ts +3 -0
  310. package/dist/src/relay/relay-service.js +5 -3
  311. package/dist/src/report-ci.d.ts +164 -0
  312. package/dist/src/report-ci.js +32 -0
  313. package/dist/src/report-effects.d.ts +15 -0
  314. package/dist/src/report-effects.js +212 -0
  315. package/dist/src/report-flow-types.d.ts +38 -0
  316. package/dist/src/report-flow-types.js +7 -0
  317. package/dist/src/report-flow.d.ts +43 -0
  318. package/dist/src/report-flow.js +193 -0
  319. package/dist/src/report-operator.d.ts +67 -0
  320. package/dist/src/report-operator.js +25 -0
  321. package/dist/src/report-quality.d.ts +221 -0
  322. package/dist/src/report-quality.js +94 -0
  323. package/dist/src/report-ready.d.ts +23 -0
  324. package/dist/src/report-ready.js +117 -0
  325. package/dist/src/report-statistics.d.ts +2 -1
  326. package/dist/src/report-statistics.js +40 -4
  327. package/dist/src/report.d.ts +588 -47
  328. package/dist/src/report.js +156 -41
  329. package/dist/src/schedule-progress.d.ts +33 -0
  330. package/dist/src/schedule-progress.js +45 -0
  331. package/dist/src/service/active-tickets.js +11 -6
  332. package/dist/src/service/command-waits.d.ts +34 -0
  333. package/dist/src/service/command-waits.js +86 -0
  334. package/dist/src/service/dispatcher.d.ts +4 -0
  335. package/dist/src/service/dispatcher.js +33 -0
  336. package/dist/src/service/expiry.js +2 -0
  337. package/dist/src/service/gateway.js +3 -1
  338. package/dist/src/service/queue-handlers.d.ts +4 -0
  339. package/dist/src/service/queue-handlers.js +32 -0
  340. package/dist/src/service/queue-spend.d.ts +6 -0
  341. package/dist/src/service/queue-spend.js +68 -0
  342. package/dist/src/service/registry.d.ts +28 -2
  343. package/dist/src/service/registry.js +48 -0
  344. package/dist/src/service/replies.d.ts +22 -8
  345. package/dist/src/service/replies.js +16 -2
  346. package/dist/src/service/run-handlers.js +37 -12
  347. package/dist/src/service/run-metadata.d.ts +15 -1
  348. package/dist/src/service/run-metadata.js +93 -8
  349. package/dist/src/service/service.d.ts +11 -3
  350. package/dist/src/service/service.js +22 -37
  351. package/dist/src/service/submission-handler.js +18 -4
  352. package/dist/src/service/worker-port.js +9 -1
  353. package/dist/src/service/worker-totals.d.ts +6 -0
  354. package/dist/src/service/worker-totals.js +8 -1
  355. package/dist/src/service/workflow-reload.d.ts +35 -0
  356. package/dist/src/service/workflow-reload.js +52 -0
  357. package/dist/src/step-bridge.d.ts +20 -0
  358. package/dist/src/steps/command-wait.d.ts +12 -0
  359. package/dist/src/steps/command-wait.js +26 -0
  360. package/dist/src/steps/open-decision-wait.d.ts +2 -0
  361. package/dist/src/steps/{decision-gate.js → open-decision-wait.js} +0 -7
  362. package/dist/src/steps/review-by-person.js +4 -1
  363. package/dist/src/steps/wait-for-command.d.ts +3 -0
  364. package/dist/src/steps/wait-for-command.js +17 -0
  365. package/dist/src/steps/wait-for-decision.js +1 -1
  366. package/dist/src/storage/command-wait-rows.d.ts +30 -0
  367. package/dist/src/storage/command-wait-rows.js +96 -0
  368. package/dist/src/storage/decision-snapshots.d.ts +8 -2
  369. package/dist/src/storage/decision-snapshots.js +16 -8
  370. package/dist/src/storage/domain-changes.d.ts +3 -1
  371. package/dist/src/storage/domain-changes.js +4 -0
  372. package/dist/src/storage/domain-schema.d.ts +16 -1
  373. package/dist/src/storage/domain-schema.js +35 -4
  374. package/dist/src/storage/effect-schema.d.ts +2 -0
  375. package/dist/src/storage/effect-schema.js +9 -0
  376. package/dist/src/storage/projection-changes.d.ts +12 -0
  377. package/dist/src/storage/projection-changes.js +194 -0
  378. package/dist/src/storage/projection-lookups.d.ts +7 -0
  379. package/dist/src/storage/projection-lookups.js +30 -0
  380. package/dist/src/testing-requests.d.ts +15 -0
  381. package/dist/src/testing-requests.js +93 -0
  382. package/dist/src/testing.d.ts +4 -10
  383. package/dist/src/testing.js +33 -86
  384. package/dist/src/worker-usage.d.ts +1 -0
  385. package/dist/src/worker-usage.js +3 -0
  386. package/dist/src/workflow-candidate.d.ts +1 -0
  387. package/dist/src/workflow-candidate.js +12 -1
  388. package/dist/src/workflow-contracts.d.ts +1 -0
  389. package/dist/src/workflow-contracts.js +9 -2
  390. package/dist/src/workflow-declaration.d.ts +1 -0
  391. package/dist/src/workflow-declaration.js +3 -0
  392. package/dist/src/workflow-discovery.d.ts +2 -13
  393. package/dist/src/workflow-discovery.js +1 -13
  394. package/dist/src/workflow-refusals.js +0 -1
  395. package/dist/src/workflows.d.ts +1 -1
  396. package/dist/src/workflows.js +1 -1
  397. package/dist/tools/compile-workflows.js +3 -0
  398. package/dist/tools/project-configuration.d.ts +7 -2
  399. package/dist/tools/project-configuration.js +30 -1
  400. package/dist/tools/red-green-command.d.ts +1 -0
  401. package/dist/tools/red-green-command.js +174 -0
  402. package/dist/tools/red-green-process.d.ts +10 -0
  403. package/dist/tools/red-green-process.js +59 -0
  404. package/dist/tools/self-host-processes.d.ts +2 -0
  405. package/dist/tools/self-host-processes.js +69 -26
  406. package/dist/tools/self-host-project.d.ts +2 -0
  407. package/dist/tools/self-host-project.js +30 -77
  408. package/dist/tools/self-host-runtime.js +1 -1
  409. package/dist/tools/self-host.js +76 -34
  410. package/dist/tools/template-command.d.ts +1 -0
  411. package/dist/tools/template-command.js +160 -0
  412. package/dist/tools/template-copy.d.ts +11 -0
  413. package/dist/tools/template-copy.js +130 -0
  414. package/dist/tools/template-files.d.ts +17 -0
  415. package/dist/tools/template-files.js +74 -0
  416. package/dist/tools/workflow-bundles.js +1 -0
  417. package/dist/tools/workflow-candidate-counts.js +2 -2
  418. package/dist/tools/workflow-candidate-program.d.ts +4 -2
  419. package/dist/tools/workflow-candidate-program.js +30 -11
  420. package/dist/tools/workflow-candidate-shape.d.ts +3 -0
  421. package/dist/tools/workflow-candidate-shape.js +90 -0
  422. package/dist/tools/workflow-candidate-source.d.ts +1 -0
  423. package/dist/tools/workflow-candidate-source.js +26 -4
  424. package/dist/tools/workflow-candidate-syntax.d.ts +2 -2
  425. package/dist/tools/workflow-candidate-syntax.js +24 -94
  426. package/dist/tools/workflow-command.js +1 -2
  427. package/dist/tools/workflow-folders.d.ts +2 -3
  428. package/dist/tools/workflow-folders.js +1 -1
  429. package/package.json +17 -9
  430. package/templates/develop/README.md +93 -4
  431. package/templates/develop/develop/flow-tests.ts +38 -1
  432. package/templates/develop/develop/interruption-tests.ts +157 -0
  433. package/templates/develop/develop/local.ts +1 -0
  434. package/templates/develop/develop/option-tests.ts +138 -9
  435. package/templates/develop/develop/pull.ts +5 -1
  436. package/templates/develop/develop/red-green.ts +55 -0
  437. package/templates/develop/develop/refusal-tests.ts +77 -0
  438. package/templates/develop/develop/report.ts +31 -11
  439. package/templates/develop/develop/round.ts +1 -0
  440. package/templates/develop/develop/scenarios.ts +27 -0
  441. package/templates/develop/develop/turn.ts +28 -7
  442. package/templates/develop/develop.declaration.json +1 -1
  443. package/templates/develop/develop.test.ts +4 -0
  444. package/templates/develop/template.json +29 -2
  445. package/templates/idea-to-issue/README.md +27 -0
  446. package/templates/idea-to-issue/idea-to-issue/effects.ts +52 -0
  447. package/templates/idea-to-issue/idea-to-issue/leave.ts +10 -0
  448. package/templates/idea-to-issue/idea-to-issue/next.ts +29 -0
  449. package/templates/idea-to-issue/idea-to-issue/post-person.ts +18 -0
  450. package/templates/idea-to-issue/idea-to-issue/post.ts +50 -0
  451. package/templates/idea-to-issue/idea-to-issue/queue.ts +27 -0
  452. package/templates/idea-to-issue/idea-to-issue/review.ts +42 -0
  453. package/templates/idea-to-issue/idea-to-issue/scenarios.ts +50 -0
  454. package/templates/idea-to-issue/idea-to-issue/turn.ts +40 -0
  455. package/templates/idea-to-issue/idea-to-issue.declaration.json +16 -0
  456. package/templates/idea-to-issue/idea-to-issue.test.ts +79 -0
  457. package/templates/idea-to-issue/idea-to-issue.ts +49 -0
  458. package/templates/idea-to-issue/template.json +81 -0
  459. package/templates/review-day/README.md +97 -0
  460. package/templates/review-day/review-day/critique.ts +58 -0
  461. package/templates/review-day/review-day/decision.ts +29 -0
  462. package/templates/review-day/review-day/facts.ts +69 -0
  463. package/templates/review-day/review-day/label.ts +29 -0
  464. package/templates/review-day/review-day/next.ts +28 -0
  465. package/templates/review-day/review-day/post.ts +27 -0
  466. package/templates/review-day/review-day/report.ts +98 -0
  467. package/templates/review-day/review-day/rules.ts +12 -0
  468. package/templates/review-day/review-day/scenarios.ts +76 -0
  469. package/templates/review-day/review-day/turn.ts +73 -0
  470. package/templates/review-day/review-day.declaration.json +16 -0
  471. package/templates/review-day/review-day.test.ts +362 -0
  472. package/templates/review-day/review-day.ts +37 -0
  473. package/templates/review-day/template.json +56 -0
  474. package/dist/src/cli/watch-start-view.d.ts +0 -18
  475. package/dist/src/cli/watch-start-view.js +0 -77
  476. package/dist/src/steps/decision-gate.d.ts +0 -3
  477. package/dist/workflows/develop-prd.d.ts +0 -7
  478. package/dist/workflows/develop-prd.js +0 -165
  479. package/dist/workflows/develop-ticket.d.ts +0 -9
  480. package/dist/workflows/develop-ticket.js +0 -207
  481. package/dist/workflows/issue-labels.d.ts +0 -16
  482. package/dist/workflows/issue-labels.js +0 -70
  483. package/dist/workflows/ticket-prompts.d.ts +0 -35
  484. package/dist/workflows/ticket-prompts.js +0 -147
@@ -1,3 +1,5 @@
1
+ import { scheduleStatusLine, scheduledText } from "./schedule-render.js";
2
+ import { CommandWaits, effectBudgetText } from "./command-waits.js";
1
3
  import { AddCard } from "./watch-card-view.js";
2
4
  import { priceText } from "./usage-render.js";
3
5
  import { component, element } from "./watch-element.js";
@@ -40,8 +42,6 @@ export function cardReview(card) {
40
42
  .join(" · ");
41
43
  }
42
44
  export function CardReview(props) {
43
- if (props.workflowId !== "develop-issue")
44
- return null;
45
45
  const review = cardReview(props.card);
46
46
  if (review === "")
47
47
  return null;
@@ -117,6 +117,12 @@ export function Card(props) {
117
117
  children: cardStep(card, now),
118
118
  }),
119
119
  component(CardReview, { card, workflowId }),
120
+ card.scheduled === undefined
121
+ ? null
122
+ : element("span", {
123
+ class: "meta",
124
+ children: scheduledText(card.scheduled),
125
+ }),
120
126
  element("span", {
121
127
  class: "card-cost",
122
128
  children: `${card.currentStep === null ? "Totals" : "Totals so far"} · ${priceText(card.claudeCostUsd, card.estimatedCostUsd)}`,
@@ -131,6 +137,11 @@ export function Card(props) {
131
137
  ],
132
138
  }),
133
139
  completionMark(card, { workflowId, now: props.observationNow ?? now }),
140
+ element("span", {
141
+ class: "meta",
142
+ children: effectBudgetText(card.spent, card.limit),
143
+ }),
144
+ component(CommandWaits, { waits: card.commandWaits ?? [] }),
134
145
  component(HandoffLinks, { run: card }),
135
146
  ],
136
147
  });
@@ -231,6 +242,12 @@ export function Board(props) {
231
242
  class: "visually-hidden",
232
243
  children: workflow.workflowId,
233
244
  }),
245
+ workflow.schedule === undefined
246
+ ? null
247
+ : element("p", {
248
+ class: "meta",
249
+ children: scheduleStatusLine(workflow.schedule, "watch"),
250
+ }),
234
251
  element("div", {
235
252
  class: "columns",
236
253
  children: workflow.columns.map((column, index) => component(Column, {
@@ -1,15 +1,17 @@
1
+ import type { ShownSchedules } from "./schedule-render.ts";
1
2
  import type { WorkflowStage, WorkflowStages } from "../workflow-declaration.ts";
2
- import type { WatchCompletionMark, WatchRun, WatchStep, WatchWorkflow } from "./watch-render.ts";
3
+ import type { WatchCompletionMark, WatchPullRequest, WatchRun, WatchStep, WatchWorkflow } from "./watch-render.ts";
3
4
  export declare function completionMarkOf(run: WatchRun, candidates: readonly WatchRun[]): WatchCompletionMark | null;
4
5
  export declare function activeRun(state: string): boolean;
5
6
  export declare function recentRun(latestAt: string | null, now: string): boolean;
6
7
  export declare function stagesFor(metadata: WorkflowStages | undefined, workflowId: string): readonly WorkflowStage[] | undefined;
7
8
  export declare function currentStepOf(events: readonly unknown[], workflow: {
8
- readonly workflowId: string;
9
+ readonly pullRequest?: WatchPullRequest | null;
9
10
  readonly stages: readonly WorkflowStage[] | undefined;
10
11
  }): WatchStep;
11
12
  export declare function roundOf(events: readonly unknown[]): number;
12
13
  interface BoardInputs {
14
+ readonly schedules?: ShownSchedules;
13
15
  readonly workflows: readonly string[];
14
16
  readonly workflowStages?: WorkflowStages;
15
17
  readonly updatedAt: string;
@@ -1,7 +1,8 @@
1
+ import { activeCommandWait, shownCommandWaits } from "./command-waits.js";
1
2
  import { linksOf } from "../authority/handoff-chain.js";
2
3
  import { record } from "../protocol.js";
3
4
  import { isInstant } from "../authority/instants.js";
4
- import { issueSteps } from "./watch-phases.js";
5
+ import { pullRequestSteps, pullRequestStepOf, stageOf, } from "./watch-phases.js";
5
6
  import { sameIssue } from "./watch-pr-identity.js";
6
7
  const weekMilliseconds = 7 * 24 * 60 * 60 * 1000;
7
8
  const terminalStates = ["completed", "failed", "cancelled"];
@@ -10,21 +11,20 @@ function eventTime(run, type) {
10
11
  return isInstant(recordedAt) ? Date.parse(recordedAt) : NaN;
11
12
  }
12
13
  export function completionMarkOf(run, candidates) {
13
- if (run.workflowId !== "develop-issue" ||
14
- run.state !== "completed" ||
15
- run.outcome !== "blocked")
14
+ if (run.state !== "completed")
16
15
  return null;
17
16
  if (run.mergeProof != null)
18
17
  return {
19
18
  text: `PR merged · #${String(run.mergeProof.number)}`,
20
19
  laterRunId: null,
21
20
  };
21
+ if (run.outcome !== "blocked")
22
+ return null;
22
23
  const completedAt = eventTime(run, "run.completed");
23
24
  const later = candidates
24
25
  .filter((candidate) => sameIssue(run.issueIdentity, candidate.issueIdentity) &&
25
26
  candidate.mergeProof != null &&
26
27
  candidate.runId !== run.runId &&
27
- candidate.workflowId === "develop-issue" &&
28
28
  candidate.state === "completed" &&
29
29
  candidate.outcome === "handoff_requested" &&
30
30
  candidate.ticketStatus !== "partial" &&
@@ -52,11 +52,11 @@ export function stagesFor(metadata, workflowId) {
52
52
  ? metadata[workflowId]
53
53
  : undefined;
54
54
  }
55
- function stepNames(workflowId, stages) {
56
- if (workflowId !== "develop-issue" && stages !== undefined)
55
+ function stepNames(workflow, stages) {
56
+ if (stages !== undefined)
57
57
  return [...stages.map((stage) => stage.label), "Other"];
58
- return workflowId === "develop-issue"
59
- ? issueSteps.map((step) => step.name)
58
+ return workflow.pullRequest != null
59
+ ? pullRequestSteps.map((step) => step.name)
60
60
  : ["Agent turn", "Your review"];
61
61
  }
62
62
  function operationOf(event) {
@@ -69,16 +69,15 @@ function operationOf(event) {
69
69
  return [];
70
70
  }
71
71
  }
72
- function stepName(event, workflowId) {
72
+ function stepName(event, workflow) {
73
73
  const type = record(event)?.["type"];
74
- if (workflowId !== "develop-issue" && type === "gate.opened")
74
+ if (workflow.pullRequest == null && type === "gate.opened")
75
75
  return "Your review";
76
76
  if (type !== "effect.requested")
77
77
  return undefined;
78
- if (workflowId !== "develop-issue")
78
+ if (workflow.pullRequest == null)
79
79
  return "Agent turn";
80
- return issueSteps.find((step) => step.operation === operationOf(event)[4])
81
- ?.name;
80
+ return pullRequestStepOf(event)?.name;
82
81
  }
83
82
  function startedAt(event) {
84
83
  const value = record(event)?.["recordedAt"];
@@ -87,30 +86,21 @@ function startedAt(event) {
87
86
  function chronological(events) {
88
87
  return events.toSorted((first, second) => Date.parse(startedAt(first) ?? "") - Date.parse(startedAt(second) ?? ""));
89
88
  }
90
- function declaredStage(event, stages) {
91
- if (record(event)?.["type"] === "effect.requested") {
92
- const operation = operationOf(event)[4];
93
- return stages.find((stage) => stage.operations?.some((name) => name === operation));
94
- }
95
- const payload = record(record(event)?.["payload"]);
96
- return (stages.find((stage) => stage.gateIds?.some((name) => name === payload?.["gateId"])) ??
97
- stages.find((stage) => stage.gateKinds?.some((name) => name === payload?.["gateKind"])));
98
- }
99
89
  function declaredStep(events, stages) {
100
90
  const event = events
101
91
  .filter((item) => ["effect.requested", "gate.opened"].includes(String(record(item)?.["type"])))
102
92
  .toSorted((first, second) => Number(record(second)?.["sequence"]) -
103
93
  Number(record(first)?.["sequence"]))[0];
104
- const stage = declaredStage(event, stages);
94
+ const stage = stageOf(event, stages);
105
95
  return { name: stage?.label ?? "Other", startedAt: startedAt(event) };
106
96
  }
107
97
  export function currentStepOf(events, workflow) {
108
- const { workflowId, stages } = workflow;
109
- if (workflowId !== "develop-issue" && stages !== undefined)
98
+ const { stages } = workflow;
99
+ if (stages !== undefined)
110
100
  return declaredStep(events, stages);
111
- const event = chronological(events).findLast((item) => stepName(item, workflowId) !== undefined);
101
+ const event = chronological(events).findLast((item) => stepName(item, workflow) !== undefined);
112
102
  return {
113
- name: stepName(event, workflowId) ?? stepNames(workflowId)[0] ?? "Agent turn",
103
+ name: stepName(event, workflow) ?? stepNames(workflow)[0] ?? "Agent turn",
114
104
  startedAt: startedAt(event),
115
105
  };
116
106
  }
@@ -129,17 +119,20 @@ function columnOf(run, now) {
129
119
  return "Stopped";
130
120
  return run.outcome === "handoff_requested" ? "Done" : null;
131
121
  }
132
- function issueCardDetails(run) {
133
- const issue = run.workflowId === "develop-issue";
122
+ function pullRequestCardDetails(run) {
134
123
  return {
135
- round: issue ? run.round : null,
136
- greptileScore: issue ? (run.reviews.at(-1)?.score ?? null) : null,
137
- pullRequestNumber: issue ? (run.pullRequest?.number ?? null) : null,
124
+ round: run.pullRequest !== null ? run.round : null,
125
+ greptileScore: run.reviews.at(-1)?.score ?? null,
126
+ pullRequestNumber: run.pullRequest?.number ?? null,
138
127
  };
139
128
  }
140
129
  function cardOf(run, runs) {
141
130
  return {
142
131
  runId: run.runId,
132
+ ...(run.scheduled === undefined ? {} : { scheduled: run.scheduled }),
133
+ commandWaits: run.commandWaits ?? [],
134
+ spent: run.spent,
135
+ limit: run.limit,
143
136
  title: run.title,
144
137
  ...linksOf(run),
145
138
  ...(run.awaitedRunId == null ? {} : { awaitedRunId: run.awaitedRunId }),
@@ -151,7 +144,7 @@ function cardOf(run, runs) {
151
144
  currentStep: activeRun(run.state) ? run.currentStep : null,
152
145
  endedAt: activeRun(run.state) ? null : run.latestAt,
153
146
  decisionWaiting: run.decisions.length > 0,
154
- ...issueCardDetails(run),
147
+ ...pullRequestCardDetails(run),
155
148
  };
156
149
  }
157
150
  function stepOrder(first, second) {
@@ -174,27 +167,43 @@ function summaryOf(value) {
174
167
  return {
175
168
  state: String(fields?.["state"]),
176
169
  waiting: (Array.isArray(pending) && pending.length > 0) ||
177
- typeof fields?.["awaitedRunId"] === "string",
170
+ typeof fields?.["awaitedRunId"] === "string" ||
171
+ activeCommandWait(shownCommandWaits(fields?.["commandWaits"])),
178
172
  latestAt: typeof latest === "string" ? latest : null,
179
173
  };
180
174
  }
175
+ function runSummaryOf(run) {
176
+ return {
177
+ state: run.state,
178
+ waiting: run.decisions.length > 0 ||
179
+ run.awaitedRunId != null ||
180
+ activeCommandWait(run.commandWaits ?? []),
181
+ latestAt: run.latestAt,
182
+ };
183
+ }
181
184
  export function boardsOf(inputs, runs) {
185
+ const schedules = inputs.schedules ?? [];
182
186
  return inputs.workflows.map((workflowId) => {
183
187
  const own = runs.filter((run) => run.workflowId === workflowId);
188
+ const schedule = "status" in schedules
189
+ ? undefined
190
+ : schedules.find((entry) => entry.workflowId === workflowId);
184
191
  const summaries = inputs.summaries === undefined
185
- ? own.map((run) => ({
186
- state: run.state,
187
- waiting: run.decisions.length > 0 || run.awaitedRunId != null,
188
- latestAt: run.latestAt,
189
- }))
192
+ ? own.map(runSummaryOf)
190
193
  : inputs.summaries
191
194
  .filter((value) => record(value)?.["workflowId"] === workflowId)
192
195
  .map(summaryOf);
193
- const columns = [
194
- ...stepNames(workflowId, stagesFor(inputs.workflowStages, workflowId)),
196
+ const names = [
197
+ ...stepNames({
198
+ pullRequest: own.find((run) => run.pullRequest !== null)?.pullRequest ?? null,
199
+ }, stagesFor(inputs.workflowStages, workflowId)),
200
+ ...own
201
+ .filter((run) => activeRun(run.state))
202
+ .map((run) => run.currentStep.name),
195
203
  "Done",
196
204
  "Stopped",
197
- ].map((name) => {
205
+ ];
206
+ const columns = [...new Set(names)].map((name) => {
198
207
  const cards = own
199
208
  .filter((run) => columnOf(run, inputs.updatedAt) === name)
200
209
  .map((run) => cardOf(run, inputs.completionCandidates ?? runs));
@@ -210,6 +219,7 @@ export function boardsOf(inputs, runs) {
210
219
  });
211
220
  return {
212
221
  workflowId,
222
+ ...(schedule === undefined ? {} : { schedule }),
213
223
  activeCount: summaries.filter((run) => activeRun(run.state)).length,
214
224
  waitingCount: summaries.filter((run) => run.waiting).length,
215
225
  latestAt: latestTime(summaries.map((run) => run.latestAt)),
@@ -1,2 +1,2 @@
1
- import { type CardForm } from "./watch-run-input.ts";
1
+ import type { CardForm } from "./watch-run-input.ts";
2
2
  export declare function watchCardForms(directory: string): Promise<readonly CardForm[]>;
@@ -1,16 +1,17 @@
1
- import { readdir, readFile } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { isWorkflowDeclaration } from "../workflow-declaration.js";
4
- import { builtinCardForms } from "./watch-run-input.js";
4
+ import { record } from "../protocol.js";
5
5
  export async function watchCardForms(directory) {
6
- const forms = [...builtinCardForms()];
7
- const files = await readdir(directory).catch(() => []);
8
- for (const file of files
9
- .filter((name) => name.endsWith(".declaration.json"))
10
- .sort()) {
11
- const value = await readFile(join(directory, file), "utf8")
12
- .then((text) => JSON.parse(text))
13
- .catch(() => null);
6
+ const forms = [];
7
+ const declarations = await readFile(join(directory, "declarations.json"), "utf8")
8
+ .then((text) => JSON.parse(text))
9
+ .catch(() => null);
10
+ if (!Array.isArray(declarations))
11
+ return [];
12
+ for (const declaration of declarations) {
13
+ const value = { ...record(declaration) };
14
+ delete value["functionName"];
14
15
  if (!isWorkflowDeclaration(value))
15
16
  continue;
16
17
  const entries = Object.entries(value.input.fields);
@@ -1 +1 @@
1
- export declare const watchClient = "\nconst state = { model: null, receivedAt: 0, stale: false, copied: \"\", label: \"\", run: \"\", loading: false, again: false, columns: {}, cards: {}, start: null, hovered: null, focused: null, phonePairing: null, phoneBusy: false, phoneMessage: \"\", phone: null, phoneLoadedAt: 0, phoneAccepted: null, phoneSequence: 0, phoneApplied: 0 };\nfunction readPreference(key) {\n try { return JSON.parse(localStorage.getItem(key)); }\n catch { return null; }\n}\nfunction readColumnChoices(key) {\n const value = readPreference(key);\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return null;\n return Object.fromEntries(Object.entries(value).filter(([, choice]) => typeof choice === \"boolean\"));\n}\nfunction savePreference(key, value) {\n try { localStorage.setItem(key, JSON.stringify(value)); }\n catch {}\n}\nfunction startControls() {\n if (state.start === null) state.start = readPreference(\"cloving.watch.start\") === true;\n return { shown: state.start, onToggle() {\n state.start = !state.start;\n savePreference(\"cloving.watch.start\", state.start);\n draw();\n } };\n}\nfunction columnControls(model, selected) {\n const workflowId = model === null ? null : boardWorkflow(model, watchLocation(selected).workflowId);\n if (workflowId === null) return undefined;\n const key = \"cloving.watch.columns:\" + workflowId;\n if (!Object.hasOwn(state.columns, workflowId))\n state.columns = { ...state.columns, [workflowId]: readColumnChoices(key) };\n const choices = state.columns[workflowId];\n return { choices, onHover(name) { state.hovered = name; }, onFocus(name) { state.focused = name; }, onToggle(name) {\n const changed = { ...choices, [name]: !columnCollapsed(choices, name) };\n state.columns = { ...state.columns, [workflowId]: changed };\n savePreference(key, changed);\n draw();\n } };\n}\nfunction moved(next, followed) {\n const arrived = next === \"waiting_gate\" && next !== state.run;\n state.run = next;\n if (arrived) scrollTo(0, 0);\n else if (followed && next === \"running\") scrollTo(0, document.body.scrollHeight);\n}\nfunction copy(command) {\n const shown = (label) => {\n state.copied = command;\n state.label = label;\n draw();\n };\n navigator.clipboard.writeText(command).then(\n () => { shown(\"Copied\"); },\n () => { shown(\"Copy failed\"); },\n );\n}\nfunction draw() {\n const { model, stale } = state;\n const selected = location.hash.slice(1);\n const followed = innerHeight + scrollY >= document.body.scrollHeight - 80;\n const copying = { command: state.copied, label: state.label, onCopy: copy };\n const now = new Date().toISOString();\n const observationNow = model === null ? now : new Date(Date.parse(model.updatedAt) + performance.now() - state.receivedAt).toISOString();\n const columns = columnControls(model, selected);\n const start = model === null ? undefined : startControls();\n const cards = cardControls(model, model === null ? null : boardWorkflow(model, watchLocation(selected).workflowId));\n const phone = model === null ? undefined : phoneControls(now);\n const page = component(Page, { model, selected, now, observationNow, stale, copying, columns, start, cards, phone });\n render(page, document.getElementById(\"app\"));\n if (model === null) return;\n document.title = watchTitle(model, selected);\n const { runId } = watchLocation(selected);\n moved(model.runs.find((run) => run.runId === runId)?.state ?? \"\", followed);\n}\nasync function withSelectedRun(model) {\n const { runId } = watchLocation(location.hash);\n if (runId === null || model.runs.some((run) => run.runId === runId)) return model;\n const detail = await fetch(\"data.json?runId=\" + encodeURIComponent(runId), { cache: \"no-store\" });\n if (detail.status === 404) return model;\n if (!detail.ok) throw new Error(\"watch-run-not-available\");\n const { run } = await detail.json();\n if (run?.runId !== runId) throw new Error(\"watch-run-mismatch\");\n return { ...model, runs: [...model.runs, run] };\n}\nasync function load() {\n state.again = state.loading;\n if (state.loading) return;\n state.loading = true;\n try {\n const reply = await fetch(\"data.json\", { cache: \"no-store\" });\n if (!reply.ok) throw new Error(\"watch-not-available\");\n const model = await reply.json();\n const receivedAt = performance.now();\n state.model = await withSelectedRun(model);\n state.receivedAt = receivedAt;\n state.stale = false;\n } catch {\n state.stale = true;\n }\n state.loading = false;\n draw();\n if (state.again) load();\n}\nfunction listen() {\n const source = new EventSource(\"events\");\n source.onmessage = () => { load(); };\n source.onerror = () => { state.stale = true; draw(); };\n}\naddEventListener(\"keydown\", (event) => {\n if (event.key !== String.fromCharCode(92) || event.defaultPrevented || event.repeat || event.isComposing) return;\n if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) return;\n if (event.target?.closest?.(\"input, textarea, select, [contenteditable]\")) return;\n const target = state.hovered ?? state.focused;\n if (target === null || watchLocation(location.hash).runId !== null) return;\n const controls = columnControls(state.model, location.hash);\n if (controls === undefined) return;\n event.preventDefault();\n controls.onToggle(target);\n});\naddEventListener(\"hashchange\", () => { state.run = \"\"; state.hovered = null; state.focused = null; draw(); load(); });\ndraw();\nlisten();\nsetInterval(draw, 1000);\nsetInterval(load, 15000);\nif (actionToken !== null) {\n phoneLoad();\n setInterval(phoneLoad, 2000);\n}\n";
1
+ export declare const watchClient = "\nconst state = { model: null, receivedAt: 0, stale: false, copied: \"\", label: \"\", run: \"\", loading: false, again: false, columns: {}, cards: {}, hovered: null, focused: null, phonePairing: null, phoneBusy: false, phoneMessage: \"\", phone: null, phoneLoadedAt: 0, phoneAccepted: null, phoneSequence: 0, phoneApplied: 0 };\nfunction readPreference(key) {\n try { return JSON.parse(localStorage.getItem(key)); }\n catch { return null; }\n}\nfunction readColumnChoices(key) {\n const value = readPreference(key);\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return null;\n return Object.fromEntries(Object.entries(value).filter(([, choice]) => typeof choice === \"boolean\"));\n}\nfunction savePreference(key, value) {\n try { localStorage.setItem(key, JSON.stringify(value)); }\n catch {}\n}\nfunction columnControls(model, selected) {\n const workflowId = model === null ? null : boardWorkflow(model, watchLocation(selected).workflowId);\n if (workflowId === null) return undefined;\n const key = \"cloving.watch.columns:\" + workflowId;\n if (!Object.hasOwn(state.columns, workflowId))\n state.columns = { ...state.columns, [workflowId]: readColumnChoices(key) };\n const choices = state.columns[workflowId];\n return { choices, onHover(name) { state.hovered = name; }, onFocus(name) { state.focused = name; }, onToggle(name) {\n const changed = { ...choices, [name]: !columnCollapsed(choices, name) };\n state.columns = { ...state.columns, [workflowId]: changed };\n savePreference(key, changed);\n draw();\n } };\n}\nfunction moved(next, followed) {\n const arrived = next === \"waiting_gate\" && next !== state.run;\n state.run = next;\n if (arrived) scrollTo(0, 0);\n else if (followed && next === \"running\") scrollTo(0, document.body.scrollHeight);\n}\nfunction copy(command) {\n const shown = (label) => {\n state.copied = command;\n state.label = label;\n draw();\n };\n navigator.clipboard.writeText(command).then(\n () => { shown(\"Copied\"); },\n () => { shown(\"Copy failed\"); },\n );\n}\nfunction draw() {\n const { model, stale } = state;\n const selected = location.hash.slice(1);\n const followed = innerHeight + scrollY >= document.body.scrollHeight - 80;\n const copying = { command: state.copied, label: state.label, onCopy: copy };\n const now = new Date().toISOString();\n const observationNow = model === null ? now : new Date(Date.parse(model.updatedAt) + performance.now() - state.receivedAt).toISOString();\n const columns = columnControls(model, selected);\n const cards = cardControls(model, model === null ? null : boardWorkflow(model, watchLocation(selected).workflowId));\n const phone = model === null ? undefined : phoneControls(now);\n const page = component(Page, { model, selected, now, observationNow, stale, copying, columns, cards, phone });\n render(page, document.getElementById(\"app\"));\n if (model === null) return;\n document.title = watchTitle(model, selected);\n const { runId } = watchLocation(selected);\n moved(model.runs.find((run) => run.runId === runId)?.state ?? \"\", followed);\n}\nasync function withSelectedRun(model) {\n const { runId } = watchLocation(location.hash);\n if (runId === null || model.runs.some((run) => run.runId === runId)) return model;\n const detail = await fetch(\"data.json?runId=\" + encodeURIComponent(runId), { cache: \"no-store\" });\n if (detail.status === 404) return model;\n if (!detail.ok) throw new Error(\"watch-run-not-available\");\n const { run } = await detail.json();\n if (run?.runId !== runId) throw new Error(\"watch-run-mismatch\");\n return { ...model, runs: [...model.runs, run] };\n}\nasync function load() {\n state.again = state.loading;\n if (state.loading) return;\n state.loading = true;\n try {\n const reply = await fetch(\"data.json\", { cache: \"no-store\" });\n if (!reply.ok) throw new Error(\"watch-not-available\");\n const model = await reply.json();\n const receivedAt = performance.now();\n state.model = await withSelectedRun(model);\n state.receivedAt = receivedAt;\n state.stale = false;\n } catch {\n state.stale = true;\n }\n state.loading = false;\n draw();\n if (state.again) load();\n}\nfunction listen() {\n const source = new EventSource(\"events\");\n source.onmessage = () => { load(); };\n source.onerror = () => { state.stale = true; draw(); };\n}\naddEventListener(\"keydown\", (event) => {\n if (event.key !== String.fromCharCode(92) || event.defaultPrevented || event.repeat || event.isComposing) return;\n if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) return;\n if (event.target?.closest?.(\"input, textarea, select, [contenteditable]\")) return;\n const target = state.hovered ?? state.focused;\n if (target === null || watchLocation(location.hash).runId !== null) return;\n const controls = columnControls(state.model, location.hash);\n if (controls === undefined) return;\n event.preventDefault();\n controls.onToggle(target);\n});\naddEventListener(\"hashchange\", () => { state.run = \"\"; state.hovered = null; state.focused = null; draw(); load(); });\ndraw();\nlisten();\nsetInterval(draw, 1000);\nsetInterval(load, 15000);\nif (actionToken !== null) {\n phoneLoad();\n setInterval(phoneLoad, 2000);\n}\n";
@@ -1,5 +1,5 @@
1
1
  export const watchClient = `
2
- const state = { model: null, receivedAt: 0, stale: false, copied: "", label: "", run: "", loading: false, again: false, columns: {}, cards: {}, start: null, hovered: null, focused: null, phonePairing: null, phoneBusy: false, phoneMessage: "", phone: null, phoneLoadedAt: 0, phoneAccepted: null, phoneSequence: 0, phoneApplied: 0 };
2
+ const state = { model: null, receivedAt: 0, stale: false, copied: "", label: "", run: "", loading: false, again: false, columns: {}, cards: {}, hovered: null, focused: null, phonePairing: null, phoneBusy: false, phoneMessage: "", phone: null, phoneLoadedAt: 0, phoneAccepted: null, phoneSequence: 0, phoneApplied: 0 };
3
3
  function readPreference(key) {
4
4
  try { return JSON.parse(localStorage.getItem(key)); }
5
5
  catch { return null; }
@@ -13,14 +13,6 @@ function savePreference(key, value) {
13
13
  try { localStorage.setItem(key, JSON.stringify(value)); }
14
14
  catch {}
15
15
  }
16
- function startControls() {
17
- if (state.start === null) state.start = readPreference("cloving.watch.start") === true;
18
- return { shown: state.start, onToggle() {
19
- state.start = !state.start;
20
- savePreference("cloving.watch.start", state.start);
21
- draw();
22
- } };
23
- }
24
16
  function columnControls(model, selected) {
25
17
  const workflowId = model === null ? null : boardWorkflow(model, watchLocation(selected).workflowId);
26
18
  if (workflowId === null) return undefined;
@@ -60,10 +52,9 @@ function draw() {
60
52
  const now = new Date().toISOString();
61
53
  const observationNow = model === null ? now : new Date(Date.parse(model.updatedAt) + performance.now() - state.receivedAt).toISOString();
62
54
  const columns = columnControls(model, selected);
63
- const start = model === null ? undefined : startControls();
64
55
  const cards = cardControls(model, model === null ? null : boardWorkflow(model, watchLocation(selected).workflowId));
65
56
  const phone = model === null ? undefined : phoneControls(now);
66
- const page = component(Page, { model, selected, now, observationNow, stale, copying, columns, start, cards, phone });
57
+ const page = component(Page, { model, selected, now, observationNow, stale, copying, columns, cards, phone });
67
58
  render(page, document.getElementById("app"));
68
59
  if (model === null) return;
69
60
  document.title = watchTitle(model, selected);
@@ -1,14 +1,9 @@
1
1
  import { type PhoneControls } from "./watch-phone-view.ts";
2
2
  import { type Shown } from "./watch-element.ts";
3
3
  import { type WatchModel } from "./watch-render.ts";
4
- export interface StartControls {
5
- readonly shown: boolean;
6
- readonly onToggle: () => void;
7
- }
8
4
  export interface HeaderProps {
9
5
  readonly model: WatchModel | null;
10
6
  readonly stale: boolean;
11
- readonly start?: StartControls;
12
7
  readonly phone?: PhoneControls | undefined;
13
8
  readonly now?: string;
14
9
  }
@@ -24,4 +19,5 @@ export declare function Queue(props: {
24
19
  export declare function Brand(): Shown;
25
20
  export declare function HeaderStatus(props: HeaderProps): Shown;
26
21
  export declare function HeaderActions(props: HeaderProps): Shown;
22
+ export declare function Version(props: HeaderProps): Shown;
27
23
  export declare function Header(props: HeaderProps): Shown;
@@ -1,3 +1,4 @@
1
+ import { queueSpendLines } from "./usage-render.js";
1
2
  import { record } from "../protocol.js";
2
3
  import { PhonePanel } from "./watch-phone-view.js";
3
4
  import { component, element } from "./watch-element.js";
@@ -15,7 +16,8 @@ export function queueLanes(model) {
15
16
  const queue = model.queue;
16
17
  if (queue === undefined)
17
18
  return null;
18
- return `Queue: ${String(queue["busy"])} of ${String(queue["lanes"])} lanes busy`;
19
+ const state = typeof queue["state"] === "string" ? `${queue["state"]}, ` : "";
20
+ return `Queue: ${state}${String(queue["busy"])} of ${String(queue["lanes"])} lanes busy`;
19
21
  }
20
22
  export function skippedText(value) {
21
23
  const ticket = record(value) ?? {};
@@ -73,6 +75,7 @@ export function HeaderStatus(props) {
73
75
  class: "header-status",
74
76
  children: [
75
77
  stamp,
78
+ ...queueSpendLines(model?.queue?.["spend"]).map((line) => element("span", { class: "queue-spend", children: line })),
76
79
  lanes === null
77
80
  ? null
78
81
  : element("span", { class: "queue-lanes", children: lanes }),
@@ -80,37 +83,30 @@ export function HeaderStatus(props) {
80
83
  });
81
84
  }
82
85
  export function HeaderActions(props) {
83
- const { start, phone } = props;
84
- if (start === undefined && phone === undefined)
86
+ const { phone } = props;
87
+ if (phone === undefined)
85
88
  return null;
86
89
  return element("div", {
87
90
  class: "header-actions",
88
- children: [
89
- start === undefined
90
- ? null
91
- : element("button", {
92
- type: "button",
93
- class: "start-toggle",
94
- "aria-controls": "start-run",
95
- "aria-expanded": String(start.shown),
96
- onClick: start.onToggle,
97
- children: "Start a run",
98
- }),
99
- phone === undefined
100
- ? null
101
- : component(PhonePanel, {
102
- phone,
103
- now: props.now ?? props.model?.updatedAt ?? "",
104
- }),
105
- ],
91
+ children: component(PhonePanel, {
92
+ phone,
93
+ now: props.now ?? props.model?.updatedAt ?? "",
94
+ }),
106
95
  });
107
96
  }
97
+ export function Version(props) {
98
+ const version = props.model?.serviceVersion;
99
+ if (version === undefined)
100
+ return null;
101
+ return element("div", { class: "version", children: `cloving ${version}` });
102
+ }
108
103
  export function Header(props) {
109
104
  return element("header", {
110
105
  children: [
111
106
  component(Brand, {}),
112
107
  component(HeaderStatus, props),
113
108
  component(HeaderActions, props),
109
+ component(Version, props),
114
110
  ],
115
111
  });
116
112
  }
@@ -1,3 +1,4 @@
1
+ import { type ShownSchedules } from "./schedule-render.ts";
1
2
  import type { WorkflowStages } from "../workflow-declaration.ts";
2
3
  import { type WorkflowRefusal } from "../workflow-refusals.ts";
3
4
  import type { WatchDecision, WatchItem, WatchModel, WatchTurn } from "./watch-render.ts";
@@ -6,16 +7,18 @@ export type { GithubRecords } from "./watch-pull-request.ts";
6
7
  import { type PrRepository } from "./watch-pr-identity.ts";
7
8
  type Fields = Readonly<Record<string, unknown>>;
8
9
  export declare function activityOf(line: string): WatchItem[];
10
+ export declare function turnActivity(lines: readonly string[]): WatchItem[];
9
11
  export interface TurnRecords {
10
12
  readonly key: string;
11
13
  readonly started: unknown;
12
14
  readonly result: unknown;
13
- readonly lines: readonly string[];
15
+ readonly activity: readonly WatchItem[];
14
16
  readonly activeAt?: string | null;
15
17
  }
16
18
  export declare function turnOf(records: TurnRecords): WatchTurn;
17
19
  export declare function decisionCommands(decision: unknown, client: string): WatchDecision;
18
20
  export interface WatchInputs {
21
+ readonly schedules?: ShownSchedules;
19
22
  readonly queue?: Fields;
20
23
  readonly repository?: PrRepository | null;
21
24
  readonly workflows: readonly string[];
@@ -26,6 +29,7 @@ export interface WatchInputs {
26
29
  readonly client: string;
27
30
  readonly updatedAt: string;
28
31
  readonly service: string;
32
+ readonly serviceVersion?: string;
29
33
  readonly runs: readonly unknown[];
30
34
  readonly completionCandidates?: readonly unknown[];
31
35
  readonly decisions: readonly unknown[];
@@ -1,3 +1,5 @@
1
+ import { shownScheduled } from "./schedule-render.js";
2
+ import { shownCommandWaits } from "./command-waits.js";
1
3
  import { operationalDecision } from "../decision-visibility.js";
2
4
  import { workflowRefusals, } from "../workflow-refusals.js";
3
5
  import { isTicketStatus } from "../ticket-status.js";
@@ -89,6 +91,9 @@ export function activityOf(line) {
89
91
  return parts(value).flatMap(failed);
90
92
  return value["type"] === "result" ? ended(value) : [];
91
93
  }
94
+ export function turnActivity(lines) {
95
+ return lines.flatMap(activityOf).slice(-activityLimit - 1);
96
+ }
92
97
  function concise(items) {
93
98
  return items.map((item, index) => {
94
99
  const before = items[index - 1];
@@ -135,8 +140,7 @@ export function turnOf(records) {
135
140
  outcome: records.result === null ? "in work" : outcome,
136
141
  costUsd: priceOf(report["costUsd"]),
137
142
  estimatedCostUsd: priceOf(report["estimatedCostUsd"]),
138
- activity: records.lines
139
- .flatMap(activityOf)
143
+ activity: records.activity
140
144
  .map((item) => item.kind === "result"
141
145
  ? {
142
146
  kind: item.kind,
@@ -203,14 +207,15 @@ function runOf(inputs, value) {
203
207
  const runId = text(status["runId"]);
204
208
  const workflowId = text(status["workflowId"]);
205
209
  const budget = record(status["budget"]);
210
+ const pull = pullOf({ records: inputs.githubRecords, repository: inputs.repository ?? null }, runId);
206
211
  const ticketId = text(status["ticketId"]);
207
- const issue = workflowId === "develop-issue"
208
- ? establishedIssue(inputs.repository ?? null, status["issueIdentity"])
209
- : null;
212
+ const issue = establishedIssue(inputs.repository ?? null, status["issueIdentity"]);
210
213
  return {
211
214
  runId,
212
215
  workflowId,
216
+ ...shownScheduled(status["scheduled"]),
213
217
  ...runLinksOf(status),
218
+ commandWaits: shownCommandWaits(status["commandWaits"]),
214
219
  ticketId: ticketId === "" ? null : ticketId,
215
220
  ...(issue === null ? {} : { issueIdentity: issue }),
216
221
  title: stamp(status["title"]),
@@ -220,7 +225,7 @@ function runOf(inputs, value) {
220
225
  estimatedCostUsd: priceOf(status["estimatedCostUsd"]),
221
226
  usage: usageOf(status["usage"]),
222
227
  currentStep: currentStepOf(list(record(value)["events"]), {
223
- workflowId,
228
+ pullRequest: pull.pullRequest,
224
229
  stages: stagesFor(inputs.workflowStages, workflowId),
225
230
  }),
226
231
  round: roundOf(list(record(value)["events"])),
@@ -237,13 +242,13 @@ function runOf(inputs, value) {
237
242
  ...(status["commandEffects"] === undefined
238
243
  ? {}
239
244
  : { commandEffects: shownCommands(status["commandEffects"]) }),
240
- phases: phasesOf(list(record(value)["events"]), workflowId),
245
+ phases: phasesOf(list(record(value)["events"]), {
246
+ pullRequest: pull.pullRequest,
247
+ stages: stagesFor(inputs.workflowStages, workflowId),
248
+ }),
241
249
  decision: decisionOf(list(record(value)["events"])),
242
250
  feedback: list(status["feedback"]).map(feedbackOf),
243
- ...pullOf({
244
- records: inputs.githubRecords,
245
- repository: workflowId === "develop-issue" ? (inputs.repository ?? null) : null,
246
- }, runId),
251
+ ...pull,
247
252
  };
248
253
  }
249
254
  function began(run) {
@@ -258,11 +263,15 @@ export function watchModel(inputs) {
258
263
  const workflows = boardsOf({ ...inputs, completionCandidates: candidates }, runs);
259
264
  return {
260
265
  workflows,
266
+ ...(inputs.schedules === undefined ? {} : { schedules: inputs.schedules }),
261
267
  ...(inputs.queue === undefined ? {} : { queue: inputs.queue }),
262
268
  workflowRefusals: workflowRefusals(inputs.workflowRefusals),
263
269
  startWorkflowId: startWorkflowOf(workflows),
264
270
  updatedAt: inputs.updatedAt,
265
271
  service: inputs.service,
272
+ ...(inputs.serviceVersion === undefined
273
+ ? {}
274
+ : { serviceVersion: inputs.serviceVersion }),
266
275
  runs: runs.toSorted((first, second) => began(first) < began(second) ? 1 : -1),
267
276
  };
268
277
  }