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
@@ -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
  }
@@ -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";
@@ -73,6 +74,7 @@ export function HeaderStatus(props) {
73
74
  class: "header-status",
74
75
  children: [
75
76
  stamp,
77
+ ...queueSpendLines(model?.queue?.["spend"]).map((line) => element("span", { class: "queue-spend", children: line })),
76
78
  lanes === null
77
79
  ? null
78
80
  : element("span", { class: "queue-lanes", children: lanes }),
@@ -80,29 +82,15 @@ export function HeaderStatus(props) {
80
82
  });
81
83
  }
82
84
  export function HeaderActions(props) {
83
- const { start, phone } = props;
84
- if (start === undefined && phone === undefined)
85
+ const { phone } = props;
86
+ if (phone === undefined)
85
87
  return null;
86
88
  return element("div", {
87
89
  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
- ],
90
+ children: component(PhonePanel, {
91
+ phone,
92
+ now: props.now ?? props.model?.updatedAt ?? "",
93
+ }),
106
94
  });
107
95
  }
108
96
  export function Header(props) {
@@ -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[];
@@ -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,6 +263,7 @@ 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),
@@ -1,3 +1,5 @@
1
+ import { scheduleStatusLine, scheduledText, shownScheduled, } from "./schedule-render.js";
2
+ import * as waitDisplay from "./command-waits.js";
1
3
  import { cardReply } from "./watch-card-reply.js";
2
4
  import { record } from "../protocol.js";
3
5
  import { Commands, commandText } from "./command-effects.js";
@@ -7,8 +9,7 @@ import { watchClient } from "./watch-client.js";
7
9
  import { watchPhoneClient } from "./watch-phone-client.js";
8
10
  import { PhoneAsk, PhoneCode, PhoneList, PhonePanel, phoneButton, phoneNotices, refusalText, timeLeft, } from "./watch-phone-view.js";
9
11
  import { priceOf, tokenCount, usageOf } from "../worker-usage.js";
10
- import { priceText, usageText, attemptText, totalsSuffix, } from "./usage-render.js";
11
- import { startPrompts, StartCard, StartRun } from "./watch-start-view.js";
12
+ import { priceText, queueSpendLines, usageText, attemptText, totalsSuffix, } from "./usage-render.js";
12
13
  import { Board, boardSizing, Card, HandoffLinks, Column, columnCollapsed, CardReview, WorkflowList, cardReview, cardStep, completionMark, observationText, staleObservation, } from "./watch-board-view.js";
13
14
  import { boardWorkflow, watchHash, watchLocation, watchTitle, } from "./watch-navigation.js";
14
15
  import { clockTime, completedDetail, counted, elapsed, groupActivity, pageTitle, phaseTime, runName, runTone, selectedRun, toneOf, turnTime, waitingDetail, } from "./watch-render.js";
@@ -16,9 +17,15 @@ import { Command, Decision, Events, Group, Item, Turn, Work, } from "./watch-tur
16
17
  import { Banner, BannerDetail, BoardPage, Page, Phases, PullRequest, Review, RefusedWorkflows, Run, } from "./watch-view.js";
17
18
  import { Brand, clove, Header, HeaderActions, HeaderStatus, Queue, queueLanes, skippedText, } from "./watch-header-view.js";
18
19
  const functions = [
20
+ scheduleStatusLine,
21
+ scheduledText,
22
+ shownScheduled,
19
23
  record,
20
24
  commandText,
21
25
  Commands,
26
+ waitDisplay.CommandWaits,
27
+ waitDisplay.commandWaitText,
28
+ waitDisplay.effectBudgetText,
22
29
  queueLanes,
23
30
  skippedText,
24
31
  Queue,
@@ -34,6 +41,7 @@ const functions = [
34
41
  tokenCount,
35
42
  usageOf,
36
43
  priceText,
44
+ queueSpendLines,
37
45
  usageText,
38
46
  attemptText,
39
47
  totalsSuffix,
@@ -73,9 +81,6 @@ const functions = [
73
81
  Command,
74
82
  Decision,
75
83
  Events,
76
- startPrompts,
77
- StartCard,
78
- StartRun,
79
84
  BannerDetail,
80
85
  Banner,
81
86
  Phases,
@@ -146,7 +151,6 @@ body { margin: 0 auto; max-width: 1800px; padding: 0 20px 96px; }
146
151
  body:has(.board-page) { padding-bottom: 0; overflow: hidden; }
147
152
  .board-page { height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
148
153
  .board-page > header, .board-page > .down { flex-shrink: 0; }
149
- .start-run { flex-shrink: 0; max-height: 45dvh; overflow-y: auto; }
150
154
  .workflow-refusals { flex-shrink: 0; max-height: 25dvh; overflow-y: auto; overflow-wrap: anywhere; }
151
155
  .workflow-refusals h2 { margin: 8px 0; }
152
156
  a { color: var(--accent-strong); }
@@ -168,12 +172,6 @@ header { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; padding
168
172
  .brand svg { width: 30px; height: 30px; color: var(--accent); }
169
173
  a:focus-visible, button:focus-visible, summary:focus-visible, input:focus-visible, textarea:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; box-shadow: 0 0 0 6px var(--code-bg); }
170
174
  h2, h3 { font-family: var(--heading-font); font-weight: 700; }
171
- .start-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); gap: 16px; margin-top: 12px; }
172
- .start-card { min-width: 0; padding: 14px; border-radius: 14px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); }
173
- .start-card h3 { margin: 0; }
174
- .start-card p { margin: 6px 0 12px; }
175
- .start-card .command { grid-template-columns: minmax(0, 1fr); margin: 0; }
176
- .start-card button { justify-self: end; }
177
175
  .workspace { display: flex; width: 100%; margin-top: 12px; flex: 1; min-height: 0; }
178
176
  .workflows { display: flex; flex-direction: row; flex-shrink: 0; gap: 8px; min-width: 0; margin-top: 12px; padding: 6px; overflow-x: auto; overflow-y: hidden; }
179
177
  .workflows a { display: grid; flex-shrink: 0; white-space: nowrap; gap: 2px; border-bottom: 3px solid transparent; padding: 10px; color: inherit; text-decoration: none; }
@@ -192,8 +190,8 @@ h2, h3 { font-family: var(--heading-font); font-weight: 700; }
192
190
  .column-cards { min-height: 0; overflow-y: auto; overscroll-behavior-y: contain; }
193
191
  .column.collapsed { flex: 0 0 ${String(boardSizing.collapsedColumnWidth)}px; min-width: ${String(boardSizing.collapsedColumnWidth)}px; padding: 6px; }
194
192
  .collapsed .column-name { writing-mode: vertical-rl; padding: 0; }
195
- header button, .phone button, .column-name button, .add-card, .add-card-form button, .start-card button { font: 500 13px/1.4 var(--body-font); color: var(--ink); background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); margin: 0; }
196
- header button:hover, .phone button:hover, .column-name button:hover, .add-card:hover, .add-card-form button:hover, .start-card button:hover { background: var(--surface-2); }
193
+ header button, .phone button, .column-name button, .add-card, .add-card-form button { font: 500 13px/1.4 var(--body-font); color: var(--ink); background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); margin: 0; }
194
+ header button:hover, .phone button:hover, .column-name button:hover, .add-card:hover, .add-card-form button:hover { background: var(--surface-2); }
197
195
  .add-card, .add-card-form button { color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); margin: 4px 8px 4px 0; }
198
196
  .add-card { width: 100%; text-align: left; margin: 4px 0; }
199
197
  .add-card-form { padding: 8px 0; overflow-wrap: anywhere; }
@@ -1,7 +1,16 @@
1
- import type { WatchPhase } from "./watch-render.ts";
2
- export declare const issueSteps: {
1
+ import type { WorkflowStage } from "../workflow-declaration.ts";
2
+ import type { WatchPhase, WatchPullRequest } from "./watch-render.ts";
3
+ export declare const pullRequestSteps: {
3
4
  name: string;
4
5
  operation: string;
5
6
  }[];
6
7
  export declare function decisionOf(events: readonly unknown[]): string;
7
- export declare function phasesOf(events: readonly unknown[], workflowId?: string): WatchPhase[];
8
+ export declare function pullRequestStepOf(event: unknown): {
9
+ name: string;
10
+ operation: string;
11
+ } | undefined;
12
+ export declare function stageOf(event: unknown, stages: readonly WorkflowStage[]): WorkflowStage | undefined;
13
+ export declare function phasesOf(events: readonly unknown[], workflow?: {
14
+ readonly pullRequest?: WatchPullRequest | null;
15
+ readonly stages?: readonly WorkflowStage[] | undefined;
16
+ }): WatchPhase[];