tumwater 0.2.0 → 0.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 (464) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +55 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +6 -4
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +404 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +65 -3
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +181 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +183 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,15 +1,18 @@
1
- // vocabulary: plain
2
- import { knownRoleIds, loadConfig } from "../config/config.js";
1
+ import { knownRoleIds, knownRoleIdsCached, loadConfig } from "../config/config.js";
3
2
  import { fail, say, sayJson } from "../cli/cli-output.js";
4
- import { failOverDurationCap, flagValue, parseDurationFlag, parseRoleFlag } from "../cli/cli-args.js";
5
- import { errorMessage } from "../text/text.js";
3
+ import { failOverDurationCap, flagValue, parseCountFlag, parseDurationFlag, parseRoleFlag } from "../cli/cli-args.js";
4
+ import { errorMessage, splitCommaList } from "../text/text.js";
6
5
  import { PAUSE_FOR_OVERFLOW_HINT, REASON_VALUE_ERROR, ROLE_VALUE_ERROR } from "../cli/cli-flag-specs.js";
7
6
  import { artifactPhrase, hasRetirableLeftovers, previewRetire, retireRole } from "./retire.js";
8
- import { revertMergedCommit } from "./revert.js";
7
+ import { previewRevert, revertFollowUp, revertMergedCommit, runRevertSha } from "./revert.js";
8
+ import { runNotFoundMessage } from "../run/run-detail.js";
9
+ import { readRunDetail } from "../run/run-detail-data.js";
10
+ import { readRunRows } from "../history/history-data.js";
11
+ import { canonicalRoleId } from "../roles/legacy-role-ids.js";
9
12
  import { agree, plural, teamResumePhrase, pauseReasonSuffix } from "../text/phrases.js";
10
13
  import { errCode } from "../errno.js";
11
14
  import { shortSha } from "../text/format.js";
12
- import { INSTANCE_ROLES } from "../roles/loop-ids.js";
15
+ import { INSTANCE_ROLES } from "../roles/runner-ids.js";
13
16
  import { allRoleIds, unknownRoleMessage } from "../roles/roles.js";
14
17
  import { normalizePauseReason, pauseTeam, pauseRole, pausedRoles, resumeTeam, resumeRole, } from "../team/team-state.js";
15
18
  import { readOrchestratorInfo } from "../team/orchestrator-info.js";
@@ -21,15 +24,15 @@ import { markerApplyNote, NO_HARNESS_ERROR, PAUSE_FOR_MAX_MS, cancelScheduledSto
21
24
  * live beside their shared `--role` resolution. The marker-writing cores and shared
22
25
  * confirmations they print live in src/operator/operator-intent.ts (shared with the
23
26
  * dashboard and TUI); the team-side consumer half is src/operator/operator-requests.ts;
24
- * the `prompt` command, which drives the durable per-loop queues rather than the marker
27
+ * the `prompt` command, which drives the durable per-role queues rather than the marker
25
28
  * protocol, lives beside them in src/inbox/prompt-commands.ts, and the `config` command,
26
29
  * which reads and writes tumwater.json directly rather than touching team state, in
27
30
  * src/cli/config-commands.ts. Every command here is deliberately usable without a live
28
31
  * harness except `abort` and `stop`, which have nothing to reach when no team is up. */
29
32
  /** The `--role <id>` value when given, resolved WITHOUT tumwater.json when it names a
30
33
  * built-in catalog role: the team itself tolerates a broken config (the live reload keeps
31
- * the last-known-good one), so an operator marker aimed at a built-in loop must not depend
32
- * on the file parsing — the same resilience `logs --role` already has. A custom-loop id or
34
+ * the last-known-good one), so an operator marker aimed at a built-in role must not depend
35
+ * on the file parsing — the same resilience `logs --transcript` already has. A custom-role id or
33
36
  * an unknown id falls through to the config-backed check, which fails with the honest error
34
37
  * (the config problem, or "unknown role" naming every valid id). Returns null when no
35
38
  * --role is given. */
@@ -46,17 +49,41 @@ function namedRole(root, args) {
46
49
  fail(errorMessage(err)); // same message main().catch would print, but via the standard fail()
47
50
  }
48
51
  }
49
- /** namedRole for the commands that write or lift the per-role pause marker: `--role feature-1`
50
- * (`bugfix-1`) is the pause key of instance 1 of a multi-instance role (src/roles/loop-ids.ts's
51
- * instancePauseKey), returned verbatim; every other value resolves through namedRole. */
52
- function pauseTargetRole(root, args) {
53
- const role = flagValue(args, "--role");
54
- if (role && [...INSTANCE_ROLES].some((base) => role === `${base}-1`))
55
- return role;
56
- return namedRole(root, args);
52
+ /** The trimmed, non-empty pieces of a comma-separated `--role` value; null when `--role` is
53
+ * absent, and a failure with ROLE_VALUE_ERROR when no piece remains (bare `--role`, `,`). */
54
+ function rolePieces(args) {
55
+ const value = flagValue(args, "--role");
56
+ if (value === null)
57
+ return null;
58
+ const pieces = splitCommaList(value ?? "");
59
+ if (pieces.length === 0)
60
+ fail(ROLE_VALUE_ERROR);
61
+ return pieces;
62
+ }
63
+ /** namedRole over a comma list (`--role clean,dedupe`): every piece resolves through namedRole
64
+ * before the caller writes anything, so a bad id writes no marker. Deduplicated in first-seen
65
+ * order; null when `--role` is absent. */
66
+ function namedRoleList(root, args) {
67
+ const pieces = rolePieces(args);
68
+ if (pieces === null)
69
+ return null;
70
+ return [...new Set(pieces.map((piece) => namedRole(root, ["--role", piece])))];
71
+ }
72
+ /** namedRole for the commands that write or lift the per-role pause marker, over a comma list
73
+ * (`--role clean,dedupe`, like `history --role`): `feature-1` (`bugfix-1`) is the pause key of
74
+ * instance 1 of a multi-instance role (src/roles/runner-ids.ts's instancePauseKey), kept
75
+ * verbatim; every other piece resolves through namedRole. Every piece resolves before the
76
+ * caller writes anything, so a bad id writes no marker. Deduplicated in first-seen order;
77
+ * null when `--role` is absent. */
78
+ function pauseTargetRoles(root, args) {
79
+ const pieces = rolePieces(args);
80
+ if (pieces === null)
81
+ return null;
82
+ const roles = pieces.map((piece) => [...INSTANCE_ROLES].some((base) => piece === `${base}-1`) ? piece : namedRole(root, ["--role", piece]));
83
+ return [...new Set(roles)];
57
84
  }
58
85
  /** The `--role <id>` value a marker command requires: namedRole's resolution plus the
59
- * command-specific "requires --role" failure. abort and retire both need a target (unlike
86
+ * command-specific "requires --role" failure. retire needs a target (unlike
60
87
  * reset-counters/resume/wake, which default to every role), so the guard lives here once and
61
88
  * names the calling command in the error. */
62
89
  function requiredNamedRole(root, args, command) {
@@ -65,27 +92,26 @@ function requiredNamedRole(root, args, command) {
65
92
  fail(`${command} requires --role <id> (e.g. \`--role feature\`)`);
66
93
  return role;
67
94
  }
68
- /** The role(s) a marker command targets: the `--role <id>` value when given, otherwise every
69
- * role in the config. reset-counters and wake share the same all-roles default, so it lives
70
- * here once instead of drifting between their bodies. */
95
+ /** The role(s) a marker command targets: the `--role <id>[,<id>...]` value when given,
96
+ * otherwise every role in the config. reset-counters and wake share the same all-roles
97
+ * default, so it lives here once instead of drifting between their bodies. */
71
98
  function targetRoles(root, args) {
72
- const role = namedRole(root, args);
73
- return role ? [role] : Object.keys(loadConfig(root).roles);
99
+ return namedRoleList(root, args) ?? Object.keys(loadConfig(root).roles);
74
100
  }
75
- /** `tumwater reset-counters [--role <id>]`: zero the per-loop counters shown in the
101
+ /** `tumwater reset-counters [--role <id>[,<id>...]]`: zero the per-role counters shown in the
76
102
  * dashboards so a fresh observation window can begin. Scheduling fields and pi session
77
- * continuity are untouched: loops keep sleeping/waking exactly as before. */
103
+ * continuity are untouched: roles keep sleeping/waking exactly as before. */
78
104
  export async function cmdResetCounters(root, args) {
79
105
  say(requestResetCounters(root, targetRoles(root, args)));
80
106
  }
81
- /** `tumwater wake [--role <id>] [--in <duration> | --cancel | --list [--json]]`: tell the
82
- * team "whatever the loops were failing on is fixed — try again", so the named roles tick
107
+ /** `tumwater wake [--role <id>[,<id>...]] [--in <duration> | --cancel | --list [--json]]`: tell the
108
+ * team "whatever the roles were failing on is fixed — try again", so the named roles run
83
109
  * within one poll instead of
84
110
  * sleeping until their backoff expires. With `--in`, the wake is scheduled: the marker is
85
111
  * written now but consumed no earlier than now + duration, the same shape `pause --for`'s
86
112
  * auto-resume takes — the wake applies at the deadline, not at submit. With `--cancel`, the
87
- * pending scheduled wakes (or the named role's share) are dropped instead; no wake is
88
- * written. With `--list`, every pending scheduled wake (or the named role's)
113
+ * pending scheduled wakes (or the named roles' share) are dropped instead; no wake is
114
+ * written. With `--list`, every pending scheduled wake (or the named roles')
89
115
  * is printed, earliest first — `--json` as an array of {roles, atMs}; read-only. */
90
116
  export async function cmdWake(root, args) {
91
117
  // `--in <duration>` (the scheduled wake): the command's own gate (runMarkerCommand's
@@ -99,9 +125,9 @@ export async function cmdWake(root, args) {
99
125
  if (inRaw !== null || args.includes("--cancel"))
100
126
  fail("wake --list cannot be combined with --in or --cancel");
101
127
  const now = Date.now();
102
- const role = namedRole(root, args) ?? undefined;
103
- const wakes = listWakes(root, role, now);
104
- say(args.includes("--json") ? JSON.stringify(wakes) : renderWakeList(wakes, now, role));
128
+ const roles = namedRoleList(root, args) ?? undefined;
129
+ const wakes = listWakes(root, roles, now);
130
+ say(args.includes("--json") ? JSON.stringify(wakes) : renderWakeList(wakes, now, roles));
105
131
  return;
106
132
  }
107
133
  if (args.includes("--json"))
@@ -109,29 +135,36 @@ export async function cmdWake(root, args) {
109
135
  if (args.includes("--cancel")) {
110
136
  if (inRaw !== null)
111
137
  fail("wake --cancel and --in cannot be combined");
112
- const role = namedRole(root, args);
113
- say(cancelWakes(root, role ?? undefined));
138
+ say(cancelWakes(root, namedRoleList(root, args) ?? undefined));
114
139
  return;
115
140
  }
116
141
  const inMs = inRaw !== null ? parseDurationFlag("wake --in", inRaw) : undefined;
117
142
  if (inMs !== undefined)
118
143
  failOverDurationCap("wake --in", inMs, PAUSE_FOR_MAX_MS);
119
144
  // requestWake takes the duration, not a deadline: it reads the clock once for both the
120
- // marker's deadline and its "wakes in" phrase, so no tick between two reads can reword
145
+ // marker's deadline and its "wakes in" phrase, so no clock advance between two reads can reword
121
146
  // "45m" as "2699999ms".
122
147
  say(requestWake(root, targetRoles(root, args), inMs));
123
148
  }
124
- /** `tumwater reclaim [--dry-run]`: run one manual reclaim pass over every harness worktree
125
- * (plans/disk-floor.md, part 3/4). `--dry-run` lists each candidate with its idle age and how
126
- * many ignored paths `git clean -ndX` would remove, and changes nothing — it neither cleans a
127
- * worktree nor writes the worktree-use registry. With a live team the
128
- * pass is requested through the marker the orchestrator consumes, so in-use worktrees are
129
- * respected; with none running it runs in-process, since nothing is in use. */
149
+ /** `tumwater reclaim [--dry-run [--json]]`: run one manual reclaim pass over every harness
150
+ * worktree (plans/disk-floor.md, part 3/4). `--dry-run` lists each candidate with its idle
151
+ * age and how many ignored paths `git clean -ndX` would remove, and changes nothing — it
152
+ * neither cleans a worktree nor writes the worktree-use registry; `--json` prints those rows
153
+ * as data. With a live team the pass is requested through the marker the orchestrator
154
+ * consumes, so in-use worktrees are respected; with none running it runs in-process, since
155
+ * nothing is in use. */
130
156
  export async function cmdReclaim(root, args) {
131
157
  const dryRun = args.includes("--dry-run");
158
+ const json = args.includes("--json");
159
+ if (json && !dryRun)
160
+ fail("reclaim --json requires --dry-run");
132
161
  const idleHours = loadConfig(root).worktreeIdleReclaimHours;
133
162
  if (dryRun) {
134
163
  const candidates = await inspectReclaimCandidates(root, idleHours);
164
+ if (json) {
165
+ sayJson({ dryRun: true, idleReclaimHours: idleHours, candidates });
166
+ return;
167
+ }
135
168
  if (candidates.length === 0) {
136
169
  say("nothing to reclaim — no reclaimable worktrees");
137
170
  return;
@@ -149,20 +182,23 @@ export async function cmdReclaim(root, args) {
149
182
  }
150
183
  say(await runManualReclaim(root));
151
184
  }
152
- /** `tumwater abort --role <id>`: kill one loop's in-flight tick right now. The CLI cannot
185
+ /** `tumwater abort --role <id>[,<id>...]`: kill the named roles' in-flight runs right now (a
186
+ * comma list writes one marker per role). The CLI cannot
153
187
  * reach into the orchestrator process, so the request rides on disk like reset-counters':
154
188
  * a per-role marker file a running team consumes within one poll cycle (the runner's
155
- * abortTick kills the pi child and resets the worktree to main). Requires a live harness —
156
- * with no team there is nothing to consume the marker. The loop stays enabled: it backs
157
- * off normally and later ticks proceed as usual. */
189
+ * abortRun kills the pi child and resets the worktree to main). Requires a live harness —
190
+ * with no team there is nothing to consume the marker. The role stays enabled: it backs
191
+ * off normally and later runs proceed as usual. */
158
192
  export async function cmdAbort(root, args) {
159
193
  // As in cmdLogs: the config exists only to validate the id against built-ins plus
160
- // user-defined loops; a missing --role fails before it is ever needed.
161
- const role = requiredNamedRole(root, args, "abort");
162
- const result = requestAbort(root, role);
163
- if (!result.ok)
164
- fail(result.error);
165
- say(result.message);
194
+ // user-defined roles; a missing --role fails before it is ever needed.
195
+ const roles = namedRoleList(root, args) ?? fail("abort requires --role <id> (e.g. `--role feature`)");
196
+ for (const role of roles) {
197
+ const result = requestAbort(root, role);
198
+ if (!result.ok)
199
+ fail(result.error);
200
+ say(result.message);
201
+ }
166
202
  }
167
203
  /** `tumwater restart`: apply a pending self-redeploy now, waiving the restart cooldown —
168
204
  * the terminal twin of the dashboard update alert's refresh button (POST /api/restart,
@@ -175,9 +211,10 @@ export async function cmdRestart(root, _args) {
175
211
  fail(result.error);
176
212
  say(result.message);
177
213
  }
178
- /** `tumwater pause [--role <id>] [--for <duration>]`: with a role, stop THAT loop from
179
- * starting new ticks — in-flight ones finish, every other role (the director included)
180
- * keeps running; without one, stop every role loop from starting NEW ticks while in-flight
214
+ /** `tumwater pause [--role <id>[,<id>...]] [--for <duration>]`: with roles (a comma list pauses
215
+ * each), stop THAT role from
216
+ * starting new runs — in-flight ones finish, every other role (the director included)
217
+ * keeps running; without one, stop every role from starting NEW runs while in-flight
181
218
  * ones finish and the director keeps running (its prompts outrank operator gates, like
182
219
  * under the budget cap). The markers
183
220
  * are persistent state, not one-shot requests: presence means paused until `resume` removes
@@ -212,8 +249,8 @@ export async function cmdPause(root, args = [], now = Date.now()) {
212
249
  // discipline timedPauseBits's own `now` parameter follows.
213
250
  const timed = forMs === undefined ? undefined : { ms: forMs, untilMs: now + forMs };
214
251
  const untilMs = timed?.untilMs;
215
- const role = pauseTargetRole(root, args);
216
- if (role) {
252
+ const roles = pauseTargetRoles(root, args);
253
+ if (roles) {
217
254
  // The plan keeps per-role pauses anonymous (a per-role reason would need a per-role map
218
255
  // for no observed need), so a reason aimed at a role pause fails fast here — accepting
219
256
  // the flag and silently dropping the note would tell the operator the reason stands
@@ -225,7 +262,8 @@ export async function cmdPause(root, args = [], now = Date.now()) {
225
262
  // the CLI, TUI, and GUI cannot drift on format or idempotence; a false return means the
226
263
  // role was already in the set, which rolePauseMessage words — unless a `--for` stands,
227
264
  // which overwrites the deadline and reports the fresh confirmation.
228
- say(rolePauseMessage(root, role, pauseRole(root, role, untilMs), timed, now));
265
+ for (const role of roles)
266
+ say(rolePauseMessage(root, role, pauseRole(root, role, untilMs), timed, now));
229
267
  return;
230
268
  }
231
269
  // pauseTeam in src/team/team-state.ts is the single writer of the pause marker — the GUI's
@@ -263,7 +301,7 @@ export function signalOrchestrator(pid) {
263
301
  throw err;
264
302
  }
265
303
  }
266
- /** `tumwater stop`: SIGTERM the running orchestrator, which drains its in-flight ticks and
304
+ /** `tumwater stop`: SIGTERM the running orchestrator, which drains its in-flight runs and
267
305
  * exits — exactly the shutdown the supervised child's own SIGINT/SIGTERM handler performs, so
268
306
  * Ctrl+C and `stop` from another terminal are the same code path (the supervisor needs no
269
307
  * signal of its own: a clean child exit already ends its loop). Unlike the marker commands
@@ -303,17 +341,19 @@ export async function cmdStop(root, args = []) {
303
341
  else
304
342
  say("the orchestrator exited before the stop signal arrived — nothing is running");
305
343
  }
306
- /** `tumwater resume [--role <id>]`: with a role, lift that one role's pause (removed from the
307
- * per-role marker set); without one, lift a team pause by removing its marker. Idempotent
308
- * like pause: with no marker there is nothing to do. No live harness required — resuming
309
- * before startup just means the next `tumwater run` starts unpaused. */
344
+ /** `tumwater resume [--role <id>[,<id>...]]`: with roles (a comma list lifts
345
+ * each), lift that role's pause (removed from the per-role marker set); without one,
346
+ * lift a team pause by removing its marker. Idempotent like pause: with no marker there
347
+ * is nothing to do. No live harness required — resuming before startup just means the
348
+ * next `tumwater run` starts unpaused. */
310
349
  export async function cmdResume(root, args = []) {
311
- const role = pauseTargetRole(root, args);
312
- if (role) {
350
+ const roles = pauseTargetRoles(root, args);
351
+ if (roles) {
313
352
  // resumeRole (src/team/team-state.ts) is the single remover of the per-role marker; a false
314
353
  // return means the role was never paused — the changed-state contract roleResumeMessage
315
354
  // words ("is already paused" vs "was not paused").
316
- say(roleResumeMessage(root, role, resumeRole(root, role)));
355
+ for (const role of roles)
356
+ say(roleResumeMessage(root, role, resumeRole(root, role)));
317
357
  return;
318
358
  }
319
359
  // resumeTeam (src/team/team-state.ts) is the single remover, shared with the GUI toggle;
@@ -325,7 +365,7 @@ export async function cmdResume(root, args = []) {
325
365
  const { when, tail } = markerApplyNote(root);
326
366
  // The per-role pause outlives a team resume (the markers are independent), so the resumed
327
367
  // team's confirmation must name the roles still individually paused instead of claiming
328
- // "role loops tick again" for loops the scheduler keeps gated.
368
+ // "roles run again" for roles the scheduler keeps gated.
329
369
  const still = pausedRoles(root);
330
370
  const roleNote = still.length > 0
331
371
  ? ` (${still.join(", ")} ${agree(still.length, "is", "are")} still individually paused — \`tumwater resume --role <id>\` lifts each)`
@@ -333,7 +373,7 @@ export async function cmdResume(root, args = []) {
333
373
  say(`${teamResumePhrase()}${when}${roleNote}${tail}`);
334
374
  }
335
375
  /** The role id retire targets: a built-in or configured id as for the other marker commands, or
336
- * else any well-formed id that still has a branch or merge ref (a loop no config names). */
376
+ * else any well-formed id that still has a branch or merge ref (a role no config names). */
337
377
  async function retireTargetRole(root, args) {
338
378
  const role = flagValue(args, "--role");
339
379
  if (role === null)
@@ -353,12 +393,12 @@ async function retireTargetRole(root, args) {
353
393
  return role;
354
394
  fail(unknownRoleMessage(role, ids));
355
395
  }
356
- /** `tumwater retire --role <id> [--force] [--json]`: remove a disabled loop's leftover worktree,
396
+ /** `tumwater retire --role <id> [--force] [--json]`: remove a disabled role's leftover worktree,
357
397
  * branch, merge ref, and paused-state marker. The collection, safety rails, and removal live in
358
398
  * src/operator/retire.ts; this is the render layer — one line per removed artifact, the skip lines
359
399
  * that make a second run idempotent, or the `--json` payload. */
360
400
  export async function cmdRetire(root, args) {
361
- // The config exists only to validate the id against built-ins plus user-defined loops, with
401
+ // The config exists only to validate the id against built-ins plus user-defined roles, with
362
402
  // the leftover-branch gate for ids no config names; a missing --role fails before it is needed.
363
403
  const role = await retireTargetRole(root, args);
364
404
  const force = args.includes("--force");
@@ -395,24 +435,95 @@ export async function cmdRetire(root, args) {
395
435
  if (result.removed.length === 0 && result.skipped.length === 0)
396
436
  say(`nothing to remove for role ${role}`);
397
437
  }
398
- /** `tumwater revert <sha>`: undo one merged change on main, committing the inverse as
399
- * `tumwater(operator): Revert <short sha>` under the primary-worktree lock. The lock, branch
400
- * guard, and fail-closed ref/conflict handling live in src/operator/revert.ts; this renderer
401
- * names the new main head and reminds the caller the reverted role may redo the work. */
438
+ const REVERT_USAGE = "usage: tumwater revert <sha>|<role> <n>|<role> --last [--dry-run] [--json]";
439
+ /** The sha the run `<role> <n>` (or the newest completed run of `<role>` under `last`) merged
440
+ * under, resolved the way `tumwater history <role> <n>` and the dashboard's revert resolve it
441
+ * (`runMergedSha`). Fails, exit 1, when the run is unknown or did not merge: revert is an
442
+ * action, so a miss is an error rather than history's exit-0 "not found". */
443
+ function revertRunSha(root, positionals, last) {
444
+ const role = canonicalRoleId(positionals[0]);
445
+ const ids = knownRoleIdsCached(root);
446
+ if (!ids.includes(role))
447
+ fail(`${unknownRoleMessage(role, ids)} (${REVERT_USAGE})`);
448
+ const run = last
449
+ ? (readRunRows(root, 1, role)[0]?.run ?? null)
450
+ : parseCountFlag("revert <n>", positionals[1]);
451
+ if (run === null)
452
+ fail(`no completed run of ${role} in the event log`);
453
+ const detail = readRunDetail(root, role, run);
454
+ if (detail === null)
455
+ fail(runNotFoundMessage(role, run));
456
+ const found = runRevertSha(role, run, detail);
457
+ if (!found.ok)
458
+ fail(found.error);
459
+ const sha = found.sha;
460
+ return { sha, line: `run ${role} #${run} merged as ${shortSha(sha)}`, role, run };
461
+ }
462
+ /** `tumwater revert <sha>|<role> <n>|<role> --last`: undo one merged change on main,
463
+ * committing the inverse as `tumwater(operator): Revert <short sha>` under the primary-worktree
464
+ * lock. The lock, branch guard, and fail-closed ref/conflict handling live in
465
+ * src/operator/revert.ts; this renderer names the new main head and names the role
466
+ * that made the reverted commit, when one did, as the one that may redo the work.
467
+ * `--json` prints the preview or the revert as one JSON object instead (refusals still fail on
468
+ * stderr). */
402
469
  export async function cmdRevert(root, args) {
403
- // No flags; exactly one positional names the commit to undo. A flag or extra positional
404
- // fails before anything is looked up or written.
405
- if (args.length !== 1)
406
- fail("revert needs a commit (e.g. run tumwater revert 1a2b3c4)");
470
+ // `--dry-run` and `--last` are the only flags; the positionals select a sha (one), a run
471
+ // (`<role> <n>`) or the newest run of a role (`<role>` with --last). A flag or any other
472
+ // shape fails before anything is looked up or written.
473
+ const dryRun = args.includes("--dry-run");
474
+ const last = args.includes("--last");
475
+ const json = args.includes("--json");
476
+ const rest = args.filter((a) => a !== "--dry-run" && a !== "--last" && a !== "--json");
407
477
  // A flag-shaped token is a dropped flag, not a commit: refuse it before rev-parse turns a
408
478
  // lone `--json` into the misleading "no such commit". Mirrors `config set`'s
409
479
  // flag-looking-value gate and gui-command.ts's --token gate, and a real sha never starts
410
480
  // with `-`.
411
- if (args[0].startsWith("-"))
412
- fail(`revert takes no flags (got ${args[0]}); pass the commit to undo as the only argument`);
413
- const result = await revertMergedCommit(root, args[0]);
481
+ const flagged = rest.find((a) => a.startsWith("-"));
482
+ if (flagged !== undefined)
483
+ fail(`revert takes only --dry-run, --last and --json (got ${flagged}); pass the commit or run to undo as the argument`);
484
+ if (last ? rest.length !== 1 : rest.length < 1 || rest.length > 2)
485
+ fail(`revert needs a commit or a run (${REVERT_USAGE})`);
486
+ const run = last || rest.length === 2 ? revertRunSha(root, rest, last) : null;
487
+ const sha = run?.sha ?? rest[0];
488
+ const runLine = run?.line ?? null;
489
+ const runRef = run === null ? null : { role: run.role, run: run.run };
490
+ if (dryRun) {
491
+ const p = await previewRevert(root, sha);
492
+ if (!p.ok)
493
+ fail(p.error);
494
+ if (json) {
495
+ sayJson({ dryRun: true, run: runRef, sha: p.sha, subject: p.subject, paths: p.paths, head: p.head, primary: p.primary, applies: p.applies, dirty: p.dirty });
496
+ if (!p.applies || p.dirty)
497
+ process.exitCode = 1;
498
+ return;
499
+ }
500
+ if (runLine !== null)
501
+ say(runLine);
502
+ say(`would revert ${shortSha(p.sha)} ${p.subject}`);
503
+ for (const path of p.paths)
504
+ say(` ${path}`);
505
+ const on = `on ${p.primary} (HEAD ${shortSha(p.head)}); nothing was written`;
506
+ if (p.applies && !p.dirty)
507
+ say(`applies cleanly ${on}`);
508
+ if (!p.applies)
509
+ say(`would conflict ${on}`);
510
+ if (p.dirty)
511
+ say("the checkout has staged or unstaged changes, so tumwater revert would refuse until they are committed or stashed; nothing was written");
512
+ if (!p.applies || p.dirty)
513
+ process.exitCode = 1;
514
+ return;
515
+ }
516
+ const result = await revertMergedCommit(root, sha);
414
517
  if (!result.ok)
415
518
  fail(result.error);
519
+ if (json) {
520
+ sayJson({ dryRun: false, run: runRef, subject: result.subject, headSha: result.headSha, beforeSha: result.beforeSha, followUp: revertFollowUp(result.subject) });
521
+ return;
522
+ }
523
+ if (runLine !== null)
524
+ say(runLine);
416
525
  say(`main is now ${shortSha(result.headSha)}`);
417
- say("the reverted role may redo the work, so cancel or edit its PLANS.md/BUGS.md entry if it should not");
526
+ const followUp = revertFollowUp(result.subject);
527
+ if (followUp !== null)
528
+ say(followUp);
418
529
  }