tumwater 0.1.2 → 0.2.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 (361) hide show
  1. package/README.md +74 -108
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +24 -0
  4. package/dist/src/agent/agent-backend.js +2 -2
  5. package/dist/src/agent/backend-traits.js +60 -0
  6. package/dist/src/agent/claude/claude-account.js +61 -8
  7. package/dist/src/agent/claude/claude-args.js +12 -15
  8. package/dist/src/agent/claude/claude-backend.js +51 -41
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +7 -16
  11. package/dist/src/agent/image-input.js +107 -0
  12. package/dist/src/agent/notes-shim.js +41 -0
  13. package/dist/src/agent/opencode/opencode-args.js +61 -0
  14. package/dist/src/agent/opencode/opencode-backend.js +255 -0
  15. package/dist/src/agent/opencode/opencode-catalog.js +184 -0
  16. package/dist/src/agent/opencode/opencode-config.js +188 -0
  17. package/dist/src/agent/opencode/opencode-env.js +36 -0
  18. package/dist/src/agent/opencode/opencode-session-id.js +37 -0
  19. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  20. package/dist/src/agent/role-notes-write.js +16 -32
  21. package/dist/src/agent/run-agent.js +95 -18
  22. package/dist/src/agent/session-ledger.js +57 -1
  23. package/dist/src/backlog/backlog-archive.js +296 -0
  24. package/dist/src/backlog/backlog-cli.js +573 -0
  25. package/dist/src/backlog/backlog-commit.js +31 -0
  26. package/dist/src/backlog/backlog-eligibility.js +357 -33
  27. package/dist/src/backlog/backlog-md.js +98 -18
  28. package/dist/src/backlog/backlog-render.js +62 -11
  29. package/dist/src/backlog/backlog-structure.js +261 -58
  30. package/dist/src/backlog/backlog-write.js +197 -319
  31. package/dist/src/backlog/backlog.js +113 -16
  32. package/dist/src/backlog/question-answer.js +339 -0
  33. package/dist/src/baseline/main-baseline.js +75 -88
  34. package/dist/src/baseline/main-red.js +38 -30
  35. package/dist/src/brief/brief-edit.js +43 -0
  36. package/dist/src/{brief.js → brief/brief.js} +66 -10
  37. package/dist/src/budget/budget.js +32 -15
  38. package/dist/src/budget/fallback-breaker.js +8 -7
  39. package/dist/src/build/build-check-detect.js +8 -7
  40. package/dist/src/build/build-check-events.js +68 -22
  41. package/dist/src/build/build-check-report.js +22 -23
  42. package/dist/src/build/build-check-scoped.js +51 -62
  43. package/dist/src/build/build-check.js +15 -14
  44. package/dist/src/build/build-info.js +2 -1
  45. package/dist/src/build/build-stage.js +23 -22
  46. package/dist/src/build/dep-install.js +22 -21
  47. package/dist/src/build/host-sleep.js +1 -1
  48. package/dist/src/change/change-data.js +25 -14
  49. package/dist/src/change/change-render.js +11 -10
  50. package/dist/src/cli/brief-command.js +58 -0
  51. package/dist/src/cli/cli-args.js +25 -5
  52. package/dist/src/cli/cli-command-args.js +46 -14
  53. package/dist/src/cli/cli-editor.js +64 -0
  54. package/dist/src/cli/cli-flag-specs.js +54 -16
  55. package/dist/src/cli/cli-marker-commands.js +13 -10
  56. package/dist/src/cli/cli-output.js +9 -0
  57. package/dist/src/cli/cli-query-commands.js +458 -49
  58. package/dist/src/cli/cli-repo-gate.js +1 -1
  59. package/dist/src/cli/cli-run.js +208 -59
  60. package/dist/src/cli/completion.js +207 -0
  61. package/dist/src/cli/config-commands.js +14 -11
  62. package/dist/src/cli/help.js +305 -110
  63. package/dist/src/cli/log-commands.js +127 -21
  64. package/dist/src/cli/notify-command.js +33 -18
  65. package/dist/src/cli/question-commands.js +29 -224
  66. package/dist/src/cli.js +121 -40
  67. package/dist/src/collections.js +66 -16
  68. package/dist/src/concurrency/check-permit.js +16 -17
  69. package/dist/src/concurrency/lock.js +6 -5
  70. package/dist/src/concurrency/provider-lease.js +69 -0
  71. package/dist/src/concurrency/semaphore.js +258 -36
  72. package/dist/src/config/config-aliases.js +106 -0
  73. package/dist/src/config/config-editable-keys.js +3 -3
  74. package/dist/src/config/config-fallbacks.js +379 -0
  75. package/dist/src/config/config-field-checks.js +19 -10
  76. package/dist/src/config/config-keys.js +160 -0
  77. package/dist/src/config/config-live.js +64 -12
  78. package/dist/src/config/config-schema.js +34 -22
  79. package/dist/src/config/config-validation.js +132 -48
  80. package/dist/src/config/config-views.js +119 -337
  81. package/dist/src/config/config-write.js +138 -177
  82. package/dist/src/config/config.js +60 -49
  83. package/dist/src/config/model-selector.js +28 -2
  84. package/dist/src/doctor/doctor-backlog.js +546 -49
  85. package/dist/src/doctor/doctor-checks.js +534 -56
  86. package/dist/src/doctor/doctor-holds.js +193 -53
  87. package/dist/src/doctor/doctor-install.js +30 -0
  88. package/dist/src/doctor/doctor-launch-services.js +2 -2
  89. package/dist/src/doctor/doctor-model-checks.js +549 -157
  90. package/dist/src/doctor/doctor-notify.js +27 -0
  91. package/dist/src/doctor/doctor-orphans.js +10 -9
  92. package/dist/src/doctor/doctor-render.js +36 -9
  93. package/dist/src/doctor/doctor.js +80 -22
  94. package/dist/src/doctor/image-probe.js +244 -0
  95. package/dist/src/doctor/probe.js +64 -0
  96. package/dist/src/events/event-format.js +226 -102
  97. package/dist/src/events/event-labels.js +116 -0
  98. package/dist/src/events/event-legacy.js +140 -0
  99. package/dist/src/events/event-read.js +58 -22
  100. package/dist/src/events/event-window.js +27 -7
  101. package/dist/src/events/events.js +18 -14
  102. package/dist/src/events/notify.js +137 -38
  103. package/dist/src/failure/failure-cluster.js +12 -9
  104. package/dist/src/failure/failure-data.js +96 -48
  105. package/dist/src/failure/failure-render.js +69 -58
  106. package/dist/src/failure/failure-state-change.js +33 -26
  107. package/dist/src/failure/time-spend.js +79 -59
  108. package/dist/src/files/file-queue.js +16 -4
  109. package/dist/src/files/files.js +18 -6
  110. package/dist/src/files/json-object.js +18 -3
  111. package/dist/src/files/tail.js +1 -1
  112. package/dist/src/gates/bootstrap-gates.js +5 -4
  113. package/dist/src/gates/budget-gates.js +250 -34
  114. package/dist/src/gates/disk-gate.js +18 -18
  115. package/dist/src/gates/gate-polls.js +117 -69
  116. package/dist/src/gates/maintenance-quota.js +29 -29
  117. package/dist/src/gates/pause-gates.js +11 -11
  118. package/dist/src/gates/readiness.js +2 -1
  119. package/dist/src/gates/role-cap-gates.js +16 -21
  120. package/dist/src/gates/startup-gate.js +75 -31
  121. package/dist/src/gates/streak-gate.js +18 -16
  122. package/dist/src/git/commit-message.js +12 -5
  123. package/dist/src/git/git-diff.js +16 -16
  124. package/dist/src/git/git-run.js +8 -8
  125. package/dist/src/git/git.js +86 -55
  126. package/dist/src/git/slots-state.js +14 -5
  127. package/dist/src/git/worktree-pool.js +25 -18
  128. package/dist/src/git/worktree-use.js +15 -14
  129. package/dist/src/git/worktree.js +18 -18
  130. package/dist/src/gui/gui-args.js +10 -14
  131. package/dist/src/gui/gui-backlog-commands.js +273 -0
  132. package/dist/src/gui/gui-command.js +1 -1
  133. package/dist/src/gui/gui-config-commands.js +440 -0
  134. package/dist/src/gui/gui-endpoint-commands.js +16 -669
  135. package/dist/src/gui/gui-endpoint-common.js +49 -0
  136. package/dist/src/gui/gui-endpoints.js +116 -56
  137. package/dist/src/gui/gui-operator-commands.js +145 -0
  138. package/dist/src/gui/gui-prompt-commands.js +234 -0
  139. package/dist/src/gui/gui-server.js +28 -8
  140. package/dist/src/gui/gui-team-commands.js +164 -0
  141. package/dist/src/gui/http-body.js +17 -1
  142. package/dist/src/history/history-data.js +194 -98
  143. package/dist/src/history/history-render.js +76 -0
  144. package/dist/src/history/history.js +37 -58
  145. package/dist/src/inbox/inbox-attachments.js +75 -14
  146. package/dist/src/inbox/inbox-cancel.js +7 -5
  147. package/dist/src/inbox/inbox-edit.js +1 -1
  148. package/dist/src/inbox/inbox-submit.js +44 -4
  149. package/dist/src/inbox/inbox-top.js +83 -0
  150. package/dist/src/inbox/inbox.js +8 -2
  151. package/dist/src/inbox/pending-prompt.js +1 -1
  152. package/dist/src/inbox/prompt-commands.js +170 -80
  153. package/dist/src/init/init-templates.js +16 -14
  154. package/dist/src/init/init.js +12 -11
  155. package/dist/src/loop/leftover.js +28 -27
  156. package/dist/src/loop/loop-fallback.js +2 -2
  157. package/dist/src/loop/loop-pi.js +106 -52
  158. package/dist/src/loop/loop-recovery.js +15 -15
  159. package/dist/src/loop/loop-state.js +139 -13
  160. package/dist/src/loop/loop-tick.js +194 -86
  161. package/dist/src/loop/loop.js +72 -36
  162. package/dist/src/loop/model-fallback.js +3 -2
  163. package/dist/src/loop/revision.js +16 -7
  164. package/dist/src/merge/backlog-conflicts.js +155 -0
  165. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
  166. package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
  167. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
  168. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
  169. package/dist/src/merge/merge-drain.js +212 -0
  170. package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
  171. package/dist/src/merge/merge-pipeline.js +99 -0
  172. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  173. package/dist/src/merge/merge-queue-render.js +75 -0
  174. package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
  175. package/dist/src/merge/merge-slot.js +340 -0
  176. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
  177. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
  178. package/dist/src/merge/merge-verify.js +212 -0
  179. package/dist/src/notes-cli.js +97 -0
  180. package/dist/src/operator/operator-commands.js +154 -47
  181. package/dist/src/operator/operator-intent.js +87 -49
  182. package/dist/src/operator/operator-requests.js +52 -52
  183. package/dist/src/operator/retire.js +62 -27
  184. package/dist/src/operator/revert.js +71 -0
  185. package/dist/src/orchestrator/orchestrator-launch.js +74 -14
  186. package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
  187. package/dist/src/orchestrator/orchestrator.js +158 -100
  188. package/dist/src/orchestrator/retention.js +29 -12
  189. package/dist/src/orchestrator/startup-migration-steps.js +51 -0
  190. package/dist/src/orchestrator/startup-migrations.js +40 -0
  191. package/dist/src/paths.js +128 -37
  192. package/dist/src/pi/pi-args.js +26 -3
  193. package/dist/src/pi/pi-backend.js +15 -23
  194. package/dist/src/pi/pi-bin.js +18 -12
  195. package/dist/src/pi/pi-event-line.js +17 -1
  196. package/dist/src/pi/pi-failure.js +87 -0
  197. package/dist/src/pi/pi-models.js +126 -38
  198. package/dist/src/pi/pi-run-result.js +1 -1
  199. package/dist/src/pi/pi-settings.js +86 -0
  200. package/dist/src/pi/pi-stream.js +131 -107
  201. package/dist/src/pi/pi-watchdogs.js +47 -43
  202. package/dist/src/pi-extension/bounded-output.js +20 -5
  203. package/dist/src/pi-extension/context-budget.js +3 -1
  204. package/dist/src/pi-extension/context-shake.js +54 -21
  205. package/dist/src/pi-extension/context-window.js +37 -0
  206. package/dist/src/pi-extension/full-output.js +2 -2
  207. package/dist/src/pi-extension/strip-thinking.js +80 -0
  208. package/dist/src/pi-extension/trim-system-prompt.js +38 -0
  209. package/dist/src/process/launch-services.js +11 -10
  210. package/dist/src/process/process-table.js +1 -1
  211. package/dist/src/process/process.js +7 -7
  212. package/dist/src/process/run-marker.js +29 -6
  213. package/dist/src/process/supervisor.js +47 -25
  214. package/dist/src/prompt/dialect.js +30 -10
  215. package/dist/src/prompt/gate-prompts.js +170 -67
  216. package/dist/src/prompt/principles.js +1 -0
  217. package/dist/src/prompt/prompt-followup.js +35 -20
  218. package/dist/src/prompt/prompt.js +249 -91
  219. package/dist/src/redeploy/redeploy-policy.js +10 -10
  220. package/dist/src/redeploy/redeploy.js +6 -4
  221. package/dist/src/redeploy/redeployer.js +63 -55
  222. package/dist/src/redeploy/self-reload.js +1 -1
  223. package/dist/src/report/report-data.js +264 -255
  224. package/dist/src/report/report-fold.js +353 -0
  225. package/dist/src/report/report-render.js +270 -33
  226. package/dist/src/request-timeouts.js +1 -1
  227. package/dist/src/review/known-flakes.js +34 -14
  228. package/dist/src/review/protected-edits.js +33 -0
  229. package/dist/src/review/review-followup.js +16 -4
  230. package/dist/src/review/review-precheck.js +14 -13
  231. package/dist/src/review/review-verdict.js +14 -0
  232. package/dist/src/review/review.js +81 -39
  233. package/dist/src/review/secret-leaks.js +127 -0
  234. package/dist/src/review/suite-rerun.js +4 -3
  235. package/dist/src/roles/legacy-role-ids.js +21 -0
  236. package/dist/src/roles/legacy-role-migration.js +62 -0
  237. package/dist/src/roles/loop-ids.js +19 -2
  238. package/dist/src/roles/role-catalog.js +125 -135
  239. package/dist/src/roles/role-guidance.js +64 -31
  240. package/dist/src/roles/role-labels.js +10 -0
  241. package/dist/src/roles/role-render.js +194 -18
  242. package/dist/src/roles/role-view.js +216 -17
  243. package/dist/src/roles/roles.js +18 -15
  244. package/dist/src/scheduling/backoff.js +29 -19
  245. package/dist/src/scheduling/claims.js +25 -9
  246. package/dist/src/scheduling/once-round.js +11 -11
  247. package/dist/src/scheduling/quiet-hours.js +25 -32
  248. package/dist/src/scheduling/scheduling.js +43 -24
  249. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  250. package/dist/src/status/status-budget.js +115 -0
  251. package/dist/src/status/status-data.js +77 -184
  252. package/dist/src/status/status-inbox.js +47 -0
  253. package/dist/src/status/status-install.js +27 -0
  254. package/dist/src/status/status-polls.js +68 -33
  255. package/dist/src/status/status-roles.js +105 -0
  256. package/dist/src/{fleet → team}/error-storm.js +10 -10
  257. package/dist/src/{fleet → team}/failure-spread.js +8 -8
  258. package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
  259. package/dist/src/{fleet → team}/reclaim.js +5 -4
  260. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
  261. package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
  262. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
  263. package/dist/src/text/datetime.js +42 -2
  264. package/dist/src/text/format.js +10 -1
  265. package/dist/src/text/markdown.js +1 -1
  266. package/dist/src/text/phrases.js +35 -34
  267. package/dist/src/text/suggest.js +3 -2
  268. package/dist/src/text/text.js +2 -2
  269. package/dist/src/tick/qa-coverage.js +2 -1
  270. package/dist/src/tick/stage-check.js +147 -26
  271. package/dist/src/tick/telemetry-digest.js +10 -9
  272. package/dist/src/tick/tick-apply.js +101 -72
  273. package/dist/src/tick/tick-detail-data.js +32 -18
  274. package/dist/src/tick/tick-detail.js +62 -21
  275. package/dist/src/tick/tick-finalize.js +29 -22
  276. package/dist/src/tick/tick-outcome.js +2 -1
  277. package/dist/src/tick/tick-prompt.js +94 -26
  278. package/dist/src/tick/tick-resume.js +29 -13
  279. package/dist/src/tick/tick-stage.js +91 -47
  280. package/dist/src/tick/tick-timing.js +60 -59
  281. package/dist/src/tick/tick-usage.js +53 -23
  282. package/dist/src/tick/tick-verdict.js +76 -10
  283. package/dist/src/ui/badges.js +118 -67
  284. package/dist/src/ui/gui/gui-client-boot.js +209 -30
  285. package/dist/src/ui/gui/gui-client-composer.js +102 -23
  286. package/dist/src/ui/gui/gui-client-drawer.js +330 -75
  287. package/dist/src/ui/gui/gui-client-history.js +96 -38
  288. package/dist/src/ui/gui/gui-client-labels.js +24 -0
  289. package/dist/src/ui/gui/gui-client-loops.js +94 -52
  290. package/dist/src/ui/gui/gui-client-markdown.js +1 -0
  291. package/dist/src/ui/gui/gui-client-model.js +63 -43
  292. package/dist/src/ui/gui/gui-client-operator.js +129 -39
  293. package/dist/src/ui/gui/gui-client-pending.js +36 -32
  294. package/dist/src/ui/gui/gui-client-report.js +339 -46
  295. package/dist/src/ui/gui/gui-client-settings.js +148 -14
  296. package/dist/src/ui/gui/gui-client-sound.js +2 -1
  297. package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
  298. package/dist/src/ui/gui/gui-client.js +26 -20
  299. package/dist/src/ui/gui/gui-page.js +19 -19
  300. package/dist/src/ui/gui/gui-styles.js +12 -4
  301. package/dist/src/ui/progress-data.js +5 -3
  302. package/dist/src/ui/status-model.js +184 -122
  303. package/dist/src/ui/status-payload.js +119 -32
  304. package/dist/src/ui/status-render.js +122 -52
  305. package/dist/src/ui/team-alerts.js +341 -0
  306. package/dist/src/ui/tick-progress-model.js +14 -14
  307. package/dist/src/ui/tone.js +11 -8
  308. package/dist/src/ui/transcript-tail.js +1 -0
  309. package/dist/src/ui/transcript.js +23 -16
  310. package/dist/src/ui/tui/tui-app.js +1 -0
  311. package/dist/src/ui/tui/tui-backlog.js +85 -11
  312. package/dist/src/ui/tui/tui-editor-answer.js +97 -0
  313. package/dist/src/ui/tui/tui-editor-attach.js +107 -0
  314. package/dist/src/ui/tui/tui-editor-brief.js +107 -0
  315. package/dist/src/ui/tui/tui-editor-budget.js +155 -0
  316. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  317. package/dist/src/ui/tui/tui-editor-entry.js +230 -0
  318. package/dist/src/ui/tui/tui-editor-interval.js +122 -0
  319. package/dist/src/ui/tui/tui-editor-notes.js +114 -0
  320. package/dist/src/ui/tui/tui-editor-pause.js +266 -0
  321. package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
  322. package/dist/src/ui/tui/tui-editor-queued.js +98 -0
  323. package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
  324. package/dist/src/ui/tui/tui-editor-retire.js +83 -0
  325. package/dist/src/ui/tui/tui-editor-search.js +73 -0
  326. package/dist/src/ui/tui/tui-editor-session.js +97 -0
  327. package/dist/src/ui/tui/tui-editors.js +152 -0
  328. package/dist/src/ui/tui/tui-frame.js +67 -55
  329. package/dist/src/ui/tui/tui-history.js +12 -7
  330. package/dist/src/ui/tui/tui-input.js +49 -44
  331. package/dist/src/ui/tui/tui-keymap.js +1 -0
  332. package/dist/src/ui/tui/tui-keys.js +91 -1704
  333. package/dist/src/ui/tui/tui-pane-activity.js +99 -0
  334. package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
  335. package/dist/src/ui/tui/tui-pane-brief.js +30 -0
  336. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  337. package/dist/src/ui/tui/tui-pane-health.js +124 -0
  338. package/dist/src/ui/tui/tui-pane-history.js +316 -0
  339. package/dist/src/ui/tui/tui-pane-pending.js +74 -0
  340. package/dist/src/ui/tui/tui-pane-queued.js +143 -0
  341. package/dist/src/ui/tui/tui-pane-report.js +122 -0
  342. package/dist/src/ui/tui/tui-pane-session.js +101 -0
  343. package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
  344. package/dist/src/ui/tui/tui-pane.js +66 -28
  345. package/dist/src/ui/tui/tui-panes.js +294 -0
  346. package/dist/src/ui/tui/tui.js +45 -25
  347. package/dist/src/verdict/fix-claim.js +141 -38
  348. package/dist/src/verdict/no-change.js +19 -0
  349. package/dist/src/verdict/refusal.js +92 -8
  350. package/dist/src/verdict/reply-contract.js +17 -5
  351. package/dist/src/version.js +3 -2
  352. package/package.json +4 -2
  353. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  354. package/dist/src/landing/backlog-conflicts.js +0 -116
  355. package/dist/src/landing/landing-drain.js +0 -204
  356. package/dist/src/landing/landing-pipeline.js +0 -96
  357. package/dist/src/landing/landing-queue-render.js +0 -31
  358. package/dist/src/landing/landing-slot.js +0 -246
  359. package/dist/src/landing/landing-vetting.js +0 -196
  360. package/dist/src/pi-extension/role-notes.js +0 -40
  361. package/dist/src/ui/fleet-alerts.js +0 -174
@@ -0,0 +1,58 @@
1
+ // vocabulary: plain
2
+ import path from "node:path";
3
+ import { readBrief, renderBrief } from "../brief/brief.js";
4
+ import { editInitialPrompt } from "../brief/brief-edit.js";
5
+ import { editInEditor } from "./cli-editor.js";
6
+ import { readPromptFileArg } from "./cli-command-args.js";
7
+ import { JSON_FLAG, rejectUnknownArgs } from "./cli-flag-specs.js";
8
+ import { fail, say, sayJson } from "./cli-output.js";
9
+ import { requireReadyRepo } from "./cli-repo-gate.js";
10
+ /** `brief`'s `--file <path>` flag: the replacement prompt (`-` for stdin). */
11
+ const FILE_SPEC = { names: ["--file"], value: true, valueName: "<path>" };
12
+ /** `brief`'s `--edit` flag: open the prompt in `$VISUAL`/`$EDITOR`. */
13
+ const EDIT_SPEC = { names: ["--edit"] };
14
+ /** `tumwater brief`: show the initial prompt and the file holding it, or with `--file <path>` to
15
+ * replace it (or with `--edit`, open it in the editor) under the checks init applies and commit
16
+ * that one file on main. */
17
+ export async function cmdBrief(root, args) {
18
+ rejectUnknownArgs("brief", args, [JSON_FLAG, FILE_SPEC, EDIT_SPEC]);
19
+ await requireReadyRepo(root);
20
+ const json = args.includes("--json");
21
+ const fileIdx = args.indexOf("--file");
22
+ const edit = args.includes("--edit");
23
+ if (edit && fileIdx >= 0)
24
+ fail("pass --edit or --file, not both");
25
+ const brief = readBrief(root);
26
+ if (brief === null)
27
+ fail("no project brief with a prompt block found — run `tumwater init` first");
28
+ if (fileIdx < 0 && !edit) {
29
+ if (json)
30
+ sayJson({ file: brief.file, prompt: brief.prompt });
31
+ else
32
+ say(renderBrief(brief));
33
+ return;
34
+ }
35
+ let text;
36
+ if (edit) {
37
+ text = editInEditor(brief.prompt, "brief.md", "pass --file <path>");
38
+ if (text.trim() === brief.prompt.trim()) {
39
+ if (json)
40
+ sayJson({ file: path.basename(brief.file), changed: false, committed: false });
41
+ else
42
+ say("brief unchanged");
43
+ return;
44
+ }
45
+ }
46
+ else
47
+ text = readPromptFileArg(args[fileIdx + 1]);
48
+ const outcome = await editInitialPrompt(root, text);
49
+ if (!outcome.ok)
50
+ fail(outcome.error);
51
+ const { file: name, changed, committed } = outcome;
52
+ if (json)
53
+ sayJson({ file: name, changed, committed });
54
+ else if (!changed)
55
+ say("brief unchanged");
56
+ else
57
+ say(`brief updated in ${name}${committed ? " (committed to main)" : ""}`);
58
+ }
@@ -3,6 +3,7 @@ import { BRANCH_VALUE_ERROR, ROLE_VALUE_ERROR } from "./cli-flag-specs.js";
3
3
  import { knownRoleIdsCached } from "../config/config.js";
4
4
  import { allRoleIds, unknownRoleMessage } from "../roles/roles.js";
5
5
  import { gotSuffix, parsePositiveInt } from "../text/text.js";
6
+ import { canonicalRoleId } from "../roles/legacy-role-ids.js";
6
7
  /** CLI argument parsing and validation, shared by every command in cli.ts. The execution
7
8
  * layer calls these before running a command, so a bad flag fails fast with an actionable
8
9
  * message instead of the command silently running with default behavior. The plain-decimal
@@ -32,7 +33,7 @@ export function flagValue(args, flag) {
32
33
  * the shared wording (`<flag> may only be given once`), `seen` being the caller's accumulator
33
34
  * for that flag. One home for the wording the spec-driven gate (cli-flag-specs.ts), the
34
35
  * free-form parsers' pre-scan (cli-command-args.ts), and the bespoke command shells
35
- * (backlog-write.ts, cli-query-commands.ts) all apply, so a repeat cannot be reported as a
36
+ * (backlog-cli.ts, cli-query-commands.ts) all apply, so a repeat cannot be reported as a
36
37
  * different error on one surface. Called per occurrence rather than pre-scanned at every site,
37
38
  * so a malformed invocation still reports whichever error its token order reaches first. */
38
39
  export function rejectRepeatFlag(seen, flag) {
@@ -59,6 +60,24 @@ export function parseGrepFlag(args, missingValueError) {
59
60
  fail(missingValueError);
60
61
  return { rest, pattern: raw };
61
62
  }
63
+ /** Parse an optional `--type <eventType>` exact-type filter flag for the event feed view
64
+ * (logs): the exact type id, or null when absent, returned alongside `rest` — the args with
65
+ * the flag's value token removed, exactly like parseGrepFlag. It mirrors that parser and not
66
+ * a bare flagValue read because the value token must leave the list: `logs --type --since`
67
+ * treats "--since" as the type text, not the flag, so the rival-flag and --since scans run
68
+ * over `rest` and cannot misfire on the pattern. A valueless or empty type fails with the
69
+ * caller's wording (each view names its own command) — the same error the `--type` gate spec
70
+ * (typeFlagSpec) reports earlier for the trailing-no-value case. */
71
+ export function parseTypeFlag(args, missingValueError) {
72
+ const i = args.indexOf("--type");
73
+ const rest = i >= 0 ? args.filter((_, j) => j !== i + 1) : args;
74
+ const raw = flagValue(args, "--type");
75
+ if (raw === null)
76
+ return { rest, type: null };
77
+ if (raw === undefined || raw === "")
78
+ fail(missingValueError);
79
+ return { rest, type: raw };
80
+ }
62
81
  /** Parse a `-n`-style count flag value: a positive integer, or fail with a clear message.
63
82
  * Unvalidated, NaN/0/negative limits make readEvents' `slice(-limit)` dump the whole log
64
83
  * (or drop leading lines) instead of showing the requested tail. With `max`, the count is
@@ -81,7 +100,7 @@ export function parseCountFlag(flag, raw, max) {
81
100
  * `(e.g. …)` example because a bare count flag's `needs a value` reads thin next to the `<n>`
82
101
  * the operator typed; `example` is the spelling shown back to them. One home for the two
83
102
  * failure messages and the plain-decimal rule, shared by the prompt queue's parser
84
- * (cli-command-args.ts) and the bug/plan backlog shells (backlog-write.ts), so the rule cannot
103
+ * (cli-command-args.ts) and the bug/plan backlog shells (backlog-cli.ts), so the rule cannot
85
104
  * drift to Number() at one site. */
86
105
  export function parsePositionFlag(flag, raw, example) {
87
106
  if (raw === undefined)
@@ -238,11 +257,12 @@ export function parseSinceFlag(args, label, maxMs) {
238
257
  * catalog alone, which keeps in-process callers (and tests) that have no config working.
239
258
  */
240
259
  export function parseRoleFlag(args, validIds) {
241
- const role = flagValue(args, "--role");
242
- if (role === null)
260
+ const typed = flagValue(args, "--role");
261
+ if (typed === null)
243
262
  return null;
244
- if (!role)
263
+ if (!typed)
245
264
  fail(ROLE_VALUE_ERROR);
265
+ const role = canonicalRoleId(typed);
246
266
  const ids = validIds ?? allRoleIds();
247
267
  if (!ids.includes(role))
248
268
  fail(unknownRoleMessage(role, ids));
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  /** The free-form-prompt commands' bespoke argument parsers: `tumwater init` and
2
3
  * `tumwater prompt`, whose positionals are prompt TEXT rather than a token list. Every
3
4
  * shared helper in cli/cli-args.ts assumes flags claim their tokens and rejects whatever is
@@ -20,7 +21,7 @@ import { errorMessage } from "../text/text.js";
20
21
  * prefix test — a negative tick number (`tick bugfix -3`) arrives flag-shaped and fails the
21
22
  * unknown-arg gate, which is the right answer for a non-positive n anyway. Lives here with
22
23
  * the CLI's other argument helpers, shared by cli.ts's tick case and
23
- * backlog-write.ts's fileAndAnnounce. */
24
+ * backlog-cli.ts's fileAndAnnounce. */
24
25
  export function peelPositionals(args) {
25
26
  const positionals = [];
26
27
  const rest = [];
@@ -28,6 +29,21 @@ export function peelPositionals(args) {
28
29
  (arg.startsWith("-") ? rest : positionals).push(arg);
29
30
  return { positionals, rest };
30
31
  }
32
+ /** The `history` listing flags that take a separate value token. */
33
+ const HISTORY_LISTING_VALUED_FLAGS = ["-n", "--since", "--grep", "--result", "--role"];
34
+ /** True when `history`'s args are the one-run trail form (`history <role> <n>`, `history
35
+ * <role> --last`): a token that is neither a flag nor the value of a listing-form valued flag.
36
+ * A valued flag's value is skipped by position, so `--grep -x` stays a listing. */
37
+ export function historyTrailForm(args) {
38
+ for (let i = 0; i < args.length; i++) {
39
+ const arg = args[i];
40
+ if (HISTORY_LISTING_VALUED_FLAGS.includes(arg))
41
+ i++;
42
+ else if (!arg.startsWith("-"))
43
+ return true;
44
+ }
45
+ return false;
46
+ }
31
47
  /** Fail when any token is not at one of the `claimed` positions — the shared "no extra tokens"
32
48
  * check for commands whose positionals are free-form prompt text (init, prompt): with their
33
49
  * flags present, every other token would be silently baked into the prompt. Names the first
@@ -206,17 +222,19 @@ const PROMPT_FLAG_SPECS = [
206
222
  JSON_FLAG,
207
223
  { names: ["--cancel"], value: true, valueName: "<n>" },
208
224
  { names: ["--edit"], value: true, valueName: "<n>" },
225
+ { names: ["--top"], value: true, valueName: "<n>" },
209
226
  { names: ["--file"], value: true, valueName: "<path>" },
210
227
  { names: ["--at"], value: true, valueName: "<duration>" },
211
228
  { names: ["--attach"], value: true, valueName: "<path>" },
212
229
  ];
213
230
  /** `tumwater prompt` argument handling, following parseInitArgs' pattern. Like init's,
214
231
  * positionals are free-form prompt content — but a double-dash token must be a real flag
215
- * (`--role <id>`, `--list`, `--json`, `--cancel <n>`, `--file <path>`, `--at <duration>`), or
232
+ * (`--role <id>`, `--list`, `--json`, `--cancel <n>`, `--edit <n>`, `--top <n>`, `--file <path>`,
233
+ * `--at <duration>`), or
216
234
  * it would be baked into the queued prompt (the same class of bug parseInitArgs fixed: today
217
235
  * `tumwater prompt --foo text` enqueues "--foo text"). Single-dash positionals remain prompt
218
- * content. `--list` and `--cancel` are mutually exclusive and may not combine with positional
219
- * text; `--role` is accepted in every mode and is never
236
+ * content. `--list`, `--cancel`, `--edit` and `--top` are mutually exclusive and may
237
+ * not combine with positional text; `--role` is accepted in every mode and is never
220
238
  * prompt content. `--at <duration>` (PLANS.md "tumwater prompt --at <duration>") only queues
221
239
  * a prompt: it defers delivery — parsed here with parseDurationFlag, riding the enqueue as
222
240
  * the queue file's not-before marker — so the read-only and destructive modes refuse it
@@ -231,6 +249,7 @@ export function parsePromptArgs(args) {
231
249
  const listFlag = args.indexOf("--list");
232
250
  const cancelFlag = args.indexOf("--cancel");
233
251
  const editFlag = args.indexOf("--edit");
252
+ const topFlag = args.indexOf("--top");
234
253
  const fileFlag = args.indexOf("--file");
235
254
  const atFlag = args.indexOf("--at");
236
255
  // --attach is repeatable: every occurrence claims itself and its value token, like --role's
@@ -244,6 +263,12 @@ export function parsePromptArgs(args) {
244
263
  fail("--list and --edit are mutually exclusive");
245
264
  if (cancelFlag >= 0 && editFlag >= 0)
246
265
  fail("--cancel and --edit are mutually exclusive");
266
+ if (listFlag >= 0 && topFlag >= 0)
267
+ fail("--list and --top are mutually exclusive");
268
+ if (cancelFlag >= 0 && topFlag >= 0)
269
+ fail("--cancel and --top are mutually exclusive");
270
+ if (editFlag >= 0 && topFlag >= 0)
271
+ fail("--edit and --top are mutually exclusive");
247
272
  const roleFlag = args.indexOf("--role");
248
273
  const roleRaw = roleFlag >= 0 ? args[roleFlag + 1] : undefined;
249
274
  if (roleFlag >= 0 && (!roleRaw || roleRaw.startsWith("--")))
@@ -259,12 +284,13 @@ export function parsePromptArgs(args) {
259
284
  // The queue-only flags --file and --at have nothing to mean in the read-only and destructive
260
285
  // modes (--list and --cancel): --file is the file-shaped twin of free-form text (--edit
261
286
  // accepts it as its replacement text, so the editFlag test below exempts that one mode), and
262
- // a delivery deferral has nothing to mean to a listing or a cancel. --attach is queue/edit-only: --list and --cancel refuse it
263
- // by name, while --edit accepts it to append images to the prompt it rewrites (the editFlag
264
- // test below exempts that one mode). One home for the mode predicate and the
265
- // `<flag> only queues a prompt` wording, so a new mode or queue-only flag is added here.
287
+ // a delivery deferral has nothing to mean to a listing or a cancel. --attach is queue/edit-only:
288
+ // --list and --cancel refuse it by name, while --edit accepts it to append images to the
289
+ // prompt it rewrites (the editFlag test below exempts that one mode). One home for the mode
290
+ // predicate and the `<flag> only queues a prompt` wording, so a new mode or queue-only flag
291
+ // is added here.
266
292
  const refuseQueueOnlyFlag = (flag, present) => {
267
- if (present && (listFlag >= 0 || cancelFlag >= 0 || editFlag >= 0))
293
+ if (present && (listFlag >= 0 || cancelFlag >= 0 || editFlag >= 0 || topFlag >= 0))
268
294
  fail(`${flag} only queues a prompt`);
269
295
  };
270
296
  refuseQueueOnlyFlag("--file", fileFlag >= 0 && editFlag < 0);
@@ -307,7 +333,7 @@ export function parsePromptArgs(args) {
307
333
  // claims. Tokens preceding --edit (other than a claimed flag) would be silently dropped —
308
334
  // a mistyped command would edit prompt N with only the trailing fragment — so they are
309
335
  // refused here by the same stray-argument wording the sibling modes use; tokens after the
310
- // position are the replacement text, minus any claimed attach pair.
336
+ // position are the replacement text, minus any claimed attach pair (none: edit in $EDITOR).
311
337
  const fileClaim = fileFlag >= 0 ? [fileFlag, fileFlag + 1] : [];
312
338
  for (let i = 0; i < editFlag; i++) {
313
339
  if (roleClaim.includes(i) || attachClaims.includes(i) || fileClaim.includes(i))
@@ -328,9 +354,15 @@ export function parsePromptArgs(args) {
328
354
  textTokens.push(args[i]);
329
355
  }
330
356
  const text = textTokens.join(" ").trim();
331
- if (!text)
332
- fail('edit text required — usage: tumwater prompt --edit <n> "<new text>" (add --role <id> to aim it at one loop)');
333
- return { mode: "edit", role, position: n, text, attachPaths };
357
+ // No text and no --file: the caller opens the prompt's current body in the editor.
358
+ return { mode: "edit", role, position: n, text: text === "" ? null : text, attachPaths };
359
+ }
360
+ if (topFlag >= 0) {
361
+ const n = parsePositionFlag("--top", args[topFlag + 1], "--top 2");
362
+ // The position is the only token --top may carry; anything else alongside it would be
363
+ // prompt text, and this mode has none.
364
+ failStrayArg(args, "with --top there is no prompt text", topFlag, topFlag + 1, ...roleClaim);
365
+ return { mode: "top", role, position: n };
334
366
  }
335
367
  if (fileFlag >= 0) {
336
368
  // The file is the prompt: read it whole (a path of `-` reads stdin, so a pipe or heredoc
@@ -353,6 +385,6 @@ export function parsePromptArgs(args) {
353
385
  // Name the fix, like the sibling operator commands' usage errors (bug/plan carry their
354
386
  // BUG_USAGE/PLAN_USAGE lines): "prompt text required" alone never said what to type.
355
387
  if (!text)
356
- fail('prompt text required — usage: tumwater prompt "<text>" (add --role <id> to aim it at one loop)');
388
+ fail('prompt text required — usage: tumwater prompt "<text>" (add --role <id> to aim it at one role)');
357
389
  return { mode: "enqueue", role, text, atDelayMs, attachPaths: attachPaths };
358
390
  }
@@ -0,0 +1,64 @@
1
+ // Open text in the operator's editor, the way `git commit` and `crontab -e` do.
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { fail } from "./cli-output.js";
7
+ /** Signal number → name, for the shell's 128+n convention: a shell that does not re-raise a
8
+ * signal-killed child (dash on Linux) exits with 128+signo, so node sees an exit status where
9
+ * bash on macOS lets it see the signal. Keyed by number, so a later alias (SIGABRT over SIGIOT)
10
+ * wins; any name is fine for the message. */
11
+ const SIGNAL_NAMES = Object.fromEntries(Object.entries(os.constants.signals).map(([name, n]) => [n, name]));
12
+ /** How an editor run ended, naming a signal even when the shell folded it into 128+n. A null
13
+ * status (spawn error, signal) and a plain non-zero status fall through unchanged. */
14
+ function exitReason(r) {
15
+ if (r.error)
16
+ return r.error.message;
17
+ if (r.signal)
18
+ return `signal ${r.signal}`;
19
+ // The editor value runs through `sh -c`, and sh reports a command it could not launch as
20
+ // 127 (not found) or 126 (found but not executable) — most often the $EDITOR/$VISUAL value
21
+ // itself is a typo or a path to a binary that is not installed. A bare "status 127" tells
22
+ // the user only that sh refused, so name the likely cause and the env var to fix, the way
23
+ // the no-editor arm does. Occasionally the 127/126 came from inside the editor's own shell
24
+ // script rather than the editor command; an occasionally-missed guess still beats a bare
25
+ // status code that never suggests anything.
26
+ if (r.status === 127)
27
+ return "the editor command was not found (exit 127) — check $EDITOR/$VISUAL";
28
+ if (r.status === 126)
29
+ return "the editor command was found but not executable (exit 126) — check $EDITOR/$VISUAL";
30
+ const signalled = r.status !== null && r.status > 128 ? SIGNAL_NAMES[r.status - 128] : undefined;
31
+ return signalled ? `signal ${signalled}` : `status ${r.status}`;
32
+ }
33
+ /** Write `initial` to a temp file named `fileName`, run `$VISUAL` (else `$EDITOR`) on it, and
34
+ * return the saved text. The editor value runs through `sh -c` (git's convention) so one with
35
+ * arguments, like `code -w`, works. There is no `vi` fallback: a script without a TTY must fail
36
+ * rather than hang. A missing editor, a spawn error, a non-zero exit, or a signal fails the
37
+ * command (`fallback` ends the no-editor message); the temp dir is removed before the failure
38
+ * exits. */
39
+ export function editInEditor(initial, fileName, fallback = "pass the new text or --file <path>") {
40
+ const editor = (process.env.VISUAL || process.env.EDITOR || "").trim();
41
+ if (editor === "")
42
+ fail(`no editor: set $VISUAL or $EDITOR, or ${fallback}`);
43
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "tumwater-edit-"));
44
+ let saved;
45
+ let failure;
46
+ try {
47
+ const file = path.join(dir, fileName);
48
+ fs.writeFileSync(file, `${initial}\n`);
49
+ const r = spawnSync("sh", ["-c", `${editor} "$1"`, "sh", file], { stdio: "inherit" });
50
+ if (r.error || r.status !== 0) {
51
+ failure = `editor exited with ${exitReason(r)}; nothing changed`;
52
+ }
53
+ else {
54
+ saved = fs.readFileSync(file, "utf8");
55
+ }
56
+ }
57
+ finally {
58
+ fs.rmSync(dir, { recursive: true, force: true });
59
+ }
60
+ // fail() exits the process, which would skip the finally above, so it runs after cleanup.
61
+ if (failure !== undefined)
62
+ fail(failure);
63
+ return saved;
64
+ }
@@ -127,15 +127,22 @@ export const PAUSE_FOR_OVERFLOW_HINT = "for a longer or standing pause run bare
127
127
  * `pause --for` honors, the same not-initialized-masking rationale durationFlagSpec records).
128
128
  * missingValue names the command for a trailing `--in` (`wake --in needs a value`), the same
129
129
  * command-named pattern durationFlagSpec and sinceFlagSpec follow. */
130
- export const WAKE_IN_FLAG = {
131
- names: ["--in"],
132
- value: true,
133
- valueName: "<duration>",
134
- missingValue: "wake --in needs a value",
135
- validate: (value) => {
136
- failOverDurationCap("wake --in", parseDurationFlag("wake --in", value), PAUSE_FOR_MAX_MS);
137
- },
138
- };
130
+ function inFlagSpec(command) {
131
+ return {
132
+ names: ["--in"],
133
+ value: true,
134
+ valueName: "<duration>",
135
+ missingValue: `${command} --in needs a value`,
136
+ validate: (value) => {
137
+ failOverDurationCap(`${command} --in`, parseDurationFlag(`${command} --in`, value), PAUSE_FOR_MAX_MS);
138
+ },
139
+ };
140
+ }
141
+ export const WAKE_IN_FLAG = inFlagSpec("wake");
142
+ /** `tumwater stop --in <duration>`: the scheduled graceful stop, same shape and cap as wake's. */
143
+ export const STOP_IN_FLAG = inFlagSpec("stop");
144
+ /** `tumwater stop --cancel`: drop the scheduled stop. */
145
+ export const STOP_CANCEL_FLAG = { names: ["--cancel"] };
139
146
  /** The `--cancel` flag spec for `tumwater wake --cancel`: drop pending scheduled wakes. */
140
147
  export const WAKE_CANCEL_FLAG = { names: ["--cancel"] };
141
148
  /** `wake --list`: print the pending scheduled wakes instead of writing one. Takes no value. */
@@ -144,7 +151,7 @@ export const WAKE_LIST_FLAG = { names: ["--list"] };
144
151
  * one definition of the flag's spelling and value shape, beside ROLE_FLAG and durationFlagSpec,
145
152
  * so the gate's accepted vocabulary cannot drift from cmdPause's parse. The missing-value
146
153
  * wording names its only command (the GREP_VALUE_ERROR idiom — each command's parser names
147
- * itself); the 200-char cap stays in pauseFleet beside the marker it bounds, and per-role
154
+ * itself); the 200-char cap stays in pauseTeam beside the marker it bounds, and per-role
148
155
  * pauses carry no reason, so cmdPause rejects the `--role` + `--reason` combination. Exported
149
156
  * so the gate and the command body share one definition of what `pause` accepts. Its
150
157
  * missing-value wording is REASON_VALUE_ERROR, exported so cmdPause's own guard reads it too. */
@@ -195,11 +202,29 @@ export const N_FLAG = {
195
202
  * siblings it needs neither a factory nor a missing-value error — so the constant is the
196
203
  * whole spec, and the render-vs-JSON switch stays in each command body beside its render. */
197
204
  export const JSON_FLAG = { names: ["--json"] };
205
+ /** The `--ready` flag spec, accepted by `backlog`: keep only the entries no hold keeps from
206
+ * being picked. Value-less, like STAT_FLAG. */
207
+ export const READY_FLAG = { names: ["--ready"] };
208
+ /** Force re-probing of every claimed image-input model in `tumwater doctor` (part 2/5),
209
+ * bypassing the 7-day cache: `--reprobe` (a value-less flag like JSON_FLAG). */
210
+ export const REPROBE_FLAG = { names: ["--reprobe"] };
211
+ /** The `--problems` flag spec, accepted by `doctor`: print only the warn and fail rows. Value-less,
212
+ * like REPROBE_FLAG. */
213
+ export const PROBLEMS_FLAG = { names: ["--problems"] };
214
+ /** The `--stat` flag spec, accepted by `diff --role`: print each patch as its `git diff --stat`
215
+ * summary. Value-less, like JSON_FLAG. */
216
+ export const STAT_FLAG = { names: ["--stat"] };
217
+ /** The `--summary` flag spec, accepted by `history`: tally each loop's tick outcomes instead of
218
+ * printing the rows. Value-less, like STAT_FLAG. */
219
+ export const SUMMARY_FLAG = { names: ["--summary"] };
220
+ /** The `--count` flag spec, accepted by `logs`: tally the selected window's event types
221
+ * instead of printing the rows. Value-less, like SUMMARY_FLAG. */
222
+ export const COUNT_FLAG = { names: ["--count"] };
198
223
  /** The `--force` flag spec, accepted by `retire` (the only override-the-rails command): one
199
224
  * definition of the flag's spelling beside ROLE_FLAG and JSON_FLAG, so the gate's accepted
200
225
  * vocabulary cannot drift from the command body's `args.includes("--force")`. */
201
226
  export const FORCE_FLAG = { names: ["--force"] };
202
- /** The `--dry-run` flag spec: preview a reclaim pass without deleting anything. Beside
227
+ /** The `--dry-run` flag spec: preview a reclaim pass or a retire without changing anything. Beside
203
228
  * FORCE_FLAG so its spelling lives with the other boolean flags the gate admits. */
204
229
  export const DRY_RUN_FLAG = { names: ["--dry-run"] };
205
230
  /** The `--last` flag spec for `tumwater tick <role> --last`: one definition of the flag's
@@ -216,6 +241,13 @@ export const LAST_FLAG = { names: ["--last"] };
216
241
  export function grepFlagSpec(missingValue) {
217
242
  return { names: ["--grep"], value: true, valueName: "<text>", missingValue };
218
243
  }
244
+ /** The `--type <event type>` flag spec of `logs`' exact-type filter, beside grepFlagSpec: one
245
+ * definition of the flag's spelling and value shape, so the gate's accepted vocabulary cannot
246
+ * drift. The missing-value wording names the command (each names its own command), so the
247
+ * spec is a factory taking it — the same string parseTypeFlag fails with in the command body. */
248
+ export function typeFlagSpec(missingValue) {
249
+ return { names: ["--type"], value: true, valueName: "<event type>", missingValue };
250
+ }
219
251
  /** The `--result <class>` flag spec of `history`'s outcome filter, beside grepFlagSpec: one
220
252
  * definition of the flag's spelling and value shape, so the gate's accepted vocabulary cannot
221
253
  * drift. The missing-value wording names the command, so the spec is a factory taking it — the
@@ -228,23 +260,29 @@ export function resultFlagSpec(missingValue) {
228
260
  * parseBranchFlag), `--once` (one full round of ticks, then exit), `--role <id>`
229
261
  * (scope a --once round to one loop, parsed by parseRoleFlag), and `--gui` (also serve the
230
262
  * browser dashboard on its defaults; port/token selection stays on `tumwater gui`). Exported
231
- * so the caller's rejectUnknownArgs check and its tests share one definition of what `run`
232
- * accepts — before this vocabulary existed, run parsed --branch and silently ignored
233
- * every other flag, so a typo'd option ran the daemon with default behavior. */
263
+ * so the caller's rejectUnknownArgs check and its tests share one definition of what `start`
264
+ * (and its hidden alias `run`) accepts — before this vocabulary existed, run parsed --branch
265
+ * and silently ignored every other flag, so a typo'd option ran the daemon with default
266
+ * behavior. */
234
267
  export const RUN_FLAG_SPECS = [
235
268
  { names: ["--branch"], value: true, valueName: "<name>", missingValue: BRANCH_VALUE_ERROR },
236
269
  { names: ["--once"] },
237
- // `run --gui` is the one-command form of the dashboard: it boots the fleet and serves the
270
+ // `run --gui` is the one-command form of the dashboard: it boots the team and serves the
238
271
  // browser dashboard at its defaults, loopback on port 7180. The dashboard's own --port,
239
272
  // --all-interfaces, and --token stay on `tumwater gui`; widening them here would mean two
240
273
  // flag vocabularies to keep in step. `run --gui --once` is refused in cmdRun beside the
241
274
  // timer it arms.
242
275
  { names: ["--gui"] },
276
+ // `run --tui` is the one-command form of the observer TUI: it boots the team and hosts the
277
+ // same in-terminal TUI `tumwater tui` attaches from another terminal. Its flag vocabulary
278
+ // stays at run's (`tumwater tui` takes no flags); `run --tui --once` is refused in cmdRun
279
+ // beside the `--gui` rival.
280
+ { names: ["--tui"] },
243
281
  // `run --for <duration>` (the windowed run): the same spec `pause --for` passes, so the
244
282
  // gate's accepted vocabulary and parseDurationFlag's error wording cannot drift; the
245
283
  // 90-day cap is the spec's own validate (shared with pause), and the --once rival rule
246
284
  // stays in cmdRun beside the timer it arms.
247
- durationFlagSpec("run --for"),
285
+ durationFlagSpec("start --for"),
248
286
  ROLE_FLAG,
249
287
  ];
250
288
  /** The one refusal for a trailing `gui --token` with no value, phrased for what the
@@ -1,6 +1,6 @@
1
1
  import { DRY_RUN_FLAG, JSON_FLAG, durationFlagSpec, PAUSE_FOR_OVERFLOW_HINT, REASON_FLAG, WAKE_CANCEL_FLAG, WAKE_LIST_FLAG, WAKE_IN_FLAG, rejectUnknownArgs, ROLE_FLAG, } from "./cli-flag-specs.js";
2
2
  import { requireReadyRepo } from "./cli-repo-gate.js";
3
- import { cmdAbort, cmdPause, cmdReclaim, cmdResetCounters, cmdResume, cmdWake } from "../operator/operator-commands.js";
3
+ import { cmdAbort, cmdPause, cmdReclaim, cmdResetCounters, cmdRestart, cmdResume, cmdWake } from "../operator/operator-commands.js";
4
4
  /** The CLI command layer of each marker command, keyed by its CLI name (the command bodies
5
5
  * live in operator/operator-commands.ts, the shared marker-writing cores in
6
6
  * operator/operator-intent.ts). One map so a new marker command registers its core beside its
@@ -12,20 +12,22 @@ const markerCommandCores = {
12
12
  pause: cmdPause,
13
13
  resume: cmdResume,
14
14
  reclaim: cmdReclaim,
15
+ restart: cmdRestart,
15
16
  };
16
- /** The shared shape of the six marker commands (reset-counters, wake, abort, pause, resume,
17
- * reclaim): reject unknown args against each command's flag set, gate on a ready repo, then
18
- * dispatch to its operator-commands core. One copy of the guard sequence so the six cannot
19
- * drift on validation order or gating. Lives beside operator/operator-commands.ts's importers
17
+ /** The shared shape of the seven marker commands (reset-counters, wake, abort, pause,
18
+ * resume, reclaim, restart): reject unknown args against each command's flag set, gate on a
19
+ * ready repo, then dispatch to its operator-commands core. One copy of the guard sequence so
20
+ * the seven cannot drift on validation order or gating. Lives beside
21
+ * operator/operator-commands.ts's importers
20
22
  * as the marker commands' CLI layer, with cli.ts's main() dispatch casting to MarkerCommand
21
23
  * here. */
22
24
  export async function runMarkerCommand(root, command, args) {
23
25
  // `pause` alone accepts `--for <duration>` (the timed pause) and `--reason <text>` (the
24
- // operator pause's why); `wake` alone accepts `--in <duration>` (the scheduled wake) and `--cancel` (drop the pending
25
- // scheduled wakes), `--list` (show them), and `--json` (with --list);
26
- // `reclaim` alone accepts `--dry-run`; the other marker commands keep the plain --role
27
- // vocabulary, so a stray --for, --reason, --in, --cancel, or --dry-run fails fast instead of being
28
- // silently ignored.
26
+ // operator pause's why); `wake` alone accepts `--in <duration>` (the scheduled wake) and
27
+ // `--cancel` (drop the pending scheduled wakes), `--list` (show them), and `--json`
28
+ // (with --list); `reclaim` alone accepts `--dry-run`; the other marker commands keep the
29
+ // plain --role vocabulary, so a stray --for, --reason, --in, --cancel, or --dry-run fails
30
+ // fast instead of being silently ignored.
29
31
  const perCommandFlags = {
30
32
  "reset-counters": [ROLE_FLAG],
31
33
  wake: [ROLE_FLAG, WAKE_IN_FLAG, WAKE_CANCEL_FLAG, WAKE_LIST_FLAG, JSON_FLAG],
@@ -37,6 +39,7 @@ export async function runMarkerCommand(root, command, args) {
37
39
  ],
38
40
  resume: [ROLE_FLAG],
39
41
  reclaim: [DRY_RUN_FLAG],
42
+ restart: [],
40
43
  };
41
44
  rejectUnknownArgs(command, args, perCommandFlags[command] ?? [ROLE_FLAG]);
42
45
  await requireReadyRepo(root);
@@ -20,6 +20,15 @@ function writeLine(text) {
20
20
  export function say(text) {
21
21
  writeLine(stripTerminalControls(text));
22
22
  }
23
+ /** Tell the operator, on stderr, that an operator backlog write landed in the working tree but
24
+ * was not committed to main (commitOperatorBacklogEdit returned false: the primary is off the
25
+ * base branch, or git failed). The write itself succeeded, so this warns instead of failing;
26
+ * a no-op when the commit landed. */
27
+ export function warnIfUncommitted(committed, file) {
28
+ if (committed)
29
+ return;
30
+ process.stderr.write(`warning: ${file} was written but not committed to main — commit it before the next fast-forward\n`);
31
+ }
23
32
  /** Print a value as pretty-printed (2-space) JSON — the `--json` output every CLI query
24
33
  * command (status, history, report, config) renders through: the pretty-print spelling
25
34
  * (indent 2) decided once so the machine-readable surface cannot drift per command. Writes