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,273 @@
1
+ // vocabulary: plain
2
+ /**
3
+ * The dashboard's POST backlog/QUESTIONS command handlers (barrelled into
4
+ * gui/gui-endpoint-commands.ts, which routes to them from src/gui/gui-server.ts):
5
+ * question-answer, backlog-file, backlog-cancel, backlog-release, backlog-top, and
6
+ * backlog-edit — the `checkedBacklogKind` / `checkedEntryPositionAndTitle` validators they share
7
+ * ride with them. Each handler adapts
8
+ * HTTP onto the domain work one layer down (backlog-write, question-answer, backlog-commit)
9
+ * and touches no socket beyond its own `res`.
10
+ */
11
+ import { commitOperatorBacklogEdit } from "../backlog/backlog-commit.js";
12
+ import { stampedSubject } from "../git/commit-message.js";
13
+ import { fileEntryWithBody, normalizeEntryBody, OPERATOR_FILE_KINDS, tryCancelEntry, tryEditEntry, tryMoveEntryToTop, tryReleaseEntry, bodyStructureProblem } from "../backlog/backlog-write.js";
14
+ import { tryAnswerQuestion, UNCLOSED_FENCE_MESSAGE } from "../backlog/question-answer.js";
15
+ import { promptLengthProblem } from "../inbox/inbox-submit.js";
16
+ import { requestWake, submitRolePromptAndWake, wakeQuestionAsker } from "../operator/operator-intent.js";
17
+ import { badRequest, okGone, sendJson } from "./http-body.js";
18
+ import { collapseWhitespace, errorMessage, gotSuffix } from "../text/text.js";
19
+ import { readPostBody } from "./gui-endpoint-common.js";
20
+ /** Shared 400-validation for an entry-target body's `kind` — "bug" or "plan" — the one field
21
+ * the backlog file/cancel/edit endpoints all ask for in the same words. Sends the 400 and
22
+ * returns null when invalid, else the validated kind. */
23
+ function checkedBacklogKind(res, body) {
24
+ const kind = body.kind;
25
+ if (kind !== "bug" && kind !== "plan") {
26
+ badRequest(res, `kind must be "bug" or "plan"${kind === undefined ? "" : gotSuffix(kind)}`);
27
+ return null;
28
+ }
29
+ return kind;
30
+ }
31
+ /** Shared 400-validation for an entry-target body's `position` (a positive integer) and
32
+ * `title` (a non-empty string) — the two fields every backlog/QUESTIONS entry drawer asks
33
+ * for, so the question-answer, backlog-cancel, backlog-release, backlog-top, and
34
+ * backlog-edit endpoints report the same two errors and cannot drift on the messages. Sends the
35
+ * 400 and returns null on the first bad field; the validated pair otherwise. */
36
+ function checkedEntryPositionAndTitle(res, body) {
37
+ const { position, title } = body;
38
+ if (typeof position !== "number" || !Number.isInteger(position) || position < 1) {
39
+ badRequest(res, `position must be a positive integer${position === undefined ? "" : gotSuffix(position)}`);
40
+ return null;
41
+ }
42
+ if (typeof title !== "string" || title === "") {
43
+ badRequest(res, `title must be a non-empty string${title === undefined ? "" : gotSuffix(title)}`);
44
+ return null;
45
+ }
46
+ return { position, title };
47
+ }
48
+ /** Run a backlog/QUESTIONS domain call, mapping a thrown filesystem/validation error onto the
49
+ * same 400 `{error}` reply the handlers' explicit validation arms send — so a thrown check and
50
+ * an explicit one answer identically and the try/catch no longer opens at every call site, one
51
+ * return each. Every try* outcome type is a union of object shapes (never null), so null is a
52
+ * safe "already answered" sentinel: the caller returns early on it, matching the `return` the
53
+ * inline try/catch used. */
54
+ function outcomeOrBadRequest(res, run) {
55
+ try {
56
+ return run();
57
+ }
58
+ catch (err) {
59
+ badRequest(res, errorMessage(err));
60
+ return null;
61
+ }
62
+ }
63
+ /** Handle POST /api/question-answer: record the operator's answer to the Nth open question
64
+ * exactly as `tumwater questions answer` does, through the non-exiting tryAnswerQuestion core.
65
+ * The body carries the `title` the operator saw at `position`; a different title there, or no
66
+ * such position, answers 200 { ok: true, status: "gone" } (the outbox moved on — data for the
67
+ * flash line, like /api/prompt-cancel). The decision keeps its lines (`questions answer --file`'s
68
+ * rule): a `## `/`### ` heading line or an unclosed fence in it answers 400 (bodyStructureProblem)
69
+ * with nothing written. An unclosed fence in `## Open` answers 409; a blank
70
+ * decision, a non-positive-integer position, or a missing title answers 400, and so does a
71
+ * filesystem failure (handleConfigSet's precedent). */
72
+ export async function handleQuestionAnswer(req, res, root) {
73
+ const body = await readPostBody(req, res, '{"position": 1, "title": "<heading>", "decision": "..."}');
74
+ if (!body)
75
+ return;
76
+ const { decision } = body;
77
+ const pair = checkedEntryPositionAndTitle(res, body);
78
+ if (!pair)
79
+ return;
80
+ const { position, title } = pair;
81
+ if (typeof decision !== "string" || decision.trim() === "") {
82
+ badRequest(res, 'questions answer needs a decision: questions answer <n> "<decision>"');
83
+ return;
84
+ }
85
+ const keptDecision = normalizeEntryBody(decision);
86
+ const problem = bodyStructureProblem(keptDecision);
87
+ if (problem !== null) {
88
+ badRequest(res, problem);
89
+ return;
90
+ }
91
+ const outcome = outcomeOrBadRequest(res, () => tryAnswerQuestion(root, position, keptDecision, title, true));
92
+ if (outcome === null)
93
+ return;
94
+ if (outcome.status === "answered") {
95
+ const committed = await commitOperatorBacklogEdit(root, "QUESTIONS.md", stampedSubject("operator", `Answer question: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
96
+ const wake = wakeQuestionAsker(root, outcome.title);
97
+ sendJson(res, 200, { ok: true, status: "answered", question: outcome.title, decision: outcome.decision, committed, ...(wake === null ? {} : { wake }) });
98
+ }
99
+ else if (outcome.status === "fenced") {
100
+ sendJson(res, 409, { ok: false, error: UNCLOSED_FENCE_MESSAGE });
101
+ }
102
+ else {
103
+ okGone(res);
104
+ }
105
+ }
106
+ /** Handle POST /api/backlog-file: the Plans/Bugs tabs' New control — the same entry and wake
107
+ * `tumwater bug`/`tumwater plan` produce (fileEntryWithBody, then the shared wake text to the
108
+ * bugfix/feature loop). Body `{"kind": "bug"|"plan", "title": "...", "body": "optional
109
+ * markdown"}`. The empty-title and wake-length checks run here, before anything is written:
110
+ * the body is normalized and checked by bodyStructureProblem too, and an over-long wake
111
+ * would throw after the entry merged. */
112
+ export async function handleBacklogFile(req, res, root) {
113
+ const body = await readPostBody(req, res, '{"kind": "plan", "title": "...", "body": "optional markdown"}');
114
+ if (!body)
115
+ return;
116
+ const kind = checkedBacklogKind(res, body);
117
+ if (!kind)
118
+ return;
119
+ const rawTitle = typeof body.title === "string" ? body.title : "";
120
+ if (collapseWhitespace(rawTitle) === "") {
121
+ badRequest(res, "title required");
122
+ return;
123
+ }
124
+ const { role, wakeText } = OPERATOR_FILE_KINDS[kind];
125
+ const problem = promptLengthProblem(wakeText(collapseWhitespace(rawTitle)), role);
126
+ if (problem) {
127
+ badRequest(res, problem);
128
+ return;
129
+ }
130
+ const rawBody = body.body;
131
+ if (rawBody !== undefined && typeof rawBody !== "string") {
132
+ badRequest(res, "body must be a string");
133
+ return;
134
+ }
135
+ const entryBody = normalizeEntryBody(rawBody ?? "");
136
+ const bodyProblem = bodyStructureProblem(entryBody);
137
+ if (bodyProblem) {
138
+ badRequest(res, bodyProblem);
139
+ return;
140
+ }
141
+ const filed = fileEntryWithBody(root, kind, rawTitle, entryBody);
142
+ const committed = await commitOperatorBacklogEdit(root, filed.file, stampedSubject("operator", `File ${kind}: ${filed.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
143
+ submitRolePromptAndWake(root, role, wakeText(filed.title));
144
+ sendJson(res, 200, { ok: true, file: filed.file, title: filed.title, committed });
145
+ }
146
+ /** Handle POST /api/backlog-cancel: the entry drawer's cancel — the same lock and splice as
147
+ * `tumwater plan|bug --cancel <n>` through the non-exiting tryCancelEntry. Body `{"kind":
148
+ * "bug"|"plan", "position": <1-based>, "title": "<heading>"}`; a missing position or a
149
+ * different heading there answers 200 { ok: true, status: "gone" } (the list moved since the
150
+ * poll). No wake and no inbox prompt: removing work must not wake a loop. */
151
+ export async function handleBacklogCancel(req, res, root) {
152
+ const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>"}');
153
+ if (!body)
154
+ return;
155
+ const kind = checkedBacklogKind(res, body);
156
+ if (!kind)
157
+ return;
158
+ const pair = checkedEntryPositionAndTitle(res, body);
159
+ if (!pair)
160
+ return;
161
+ const { position, title } = pair;
162
+ const outcome = outcomeOrBadRequest(res, () => tryCancelEntry(root, kind, position, title));
163
+ if (outcome === null)
164
+ return;
165
+ if (outcome.status === "cancelled") {
166
+ const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Cancel ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
167
+ sendJson(res, 200, { ok: true, status: "cancelled", file: outcome.file, title: outcome.title, committed });
168
+ }
169
+ else {
170
+ okGone(res);
171
+ }
172
+ }
173
+ /** Handle POST /api/backlog-release: the entry drawer's release — the same lock and splice as
174
+ * `tumwater plan|bug --release <n>` through the non-exiting tryReleaseEntry. Body as
175
+ * backlog-cancel's. A released entry is newly eligible, so the kind's role is woken; `gone`
176
+ * and `moved` answer okGone, and an entry with neither a Refused nor a Waiting-for-the-next-
177
+ * failure note answers status "not_refused" with no write and no wake. */
178
+ export async function handleBacklogRelease(req, res, root) {
179
+ const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>"}');
180
+ if (!body)
181
+ return;
182
+ const kind = checkedBacklogKind(res, body);
183
+ if (!kind)
184
+ return;
185
+ const pair = checkedEntryPositionAndTitle(res, body);
186
+ if (!pair)
187
+ return;
188
+ const { position, title } = pair;
189
+ const outcome = outcomeOrBadRequest(res, () => tryReleaseEntry(root, kind, position, title));
190
+ if (outcome === null)
191
+ return;
192
+ if (outcome.status === "released") {
193
+ const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Release ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
194
+ requestWake(root, [OPERATOR_FILE_KINDS[kind].role]);
195
+ sendJson(res, 200, { ok: true, status: "released", file: outcome.file, title: outcome.title, committed });
196
+ }
197
+ else if (outcome.status === "not_refused") {
198
+ sendJson(res, 200, { ok: true, status: "not_refused", title: outcome.title });
199
+ }
200
+ else {
201
+ okGone(res);
202
+ }
203
+ }
204
+ /** Handle POST /api/backlog-top: the entry drawer's Move to top — the same splice as
205
+ * `tumwater plan|bug --top <n>` through the non-exiting tryMoveEntryToTop. Body as
206
+ * backlog-cancel's. Reordering makes no entry newly eligible, so no role is woken; position 1
207
+ * answers status "already_first" with no write, and `gone` and `moved` answer okGone. */
208
+ export async function handleBacklogTop(req, res, root) {
209
+ const body = await readPostBody(req, res, '{"kind": "bug", "position": 2, "title": "<heading>"}');
210
+ if (!body)
211
+ return;
212
+ const kind = checkedBacklogKind(res, body);
213
+ if (!kind)
214
+ return;
215
+ const pair = checkedEntryPositionAndTitle(res, body);
216
+ if (!pair)
217
+ return;
218
+ const { position, title } = pair;
219
+ const outcome = outcomeOrBadRequest(res, () => tryMoveEntryToTop(root, kind, position, title));
220
+ if (outcome === null)
221
+ return;
222
+ if (outcome.status === "moved_to_top") {
223
+ const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Prioritize ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
224
+ sendJson(res, 200, { ok: true, status: "moved_to_top", file: outcome.file, title: outcome.title, committed });
225
+ }
226
+ else if (outcome.status === "already_first") {
227
+ sendJson(res, 200, { ok: true, status: "already_first", title: outcome.title });
228
+ }
229
+ else {
230
+ okGone(res);
231
+ }
232
+ }
233
+ /** Handle POST /api/backlog-edit: the entry drawer's edit — tryEditEntry's title-checked,
234
+ * in-place rewrite under the state lock. Body `{"kind", "position", "title", "newTitle",
235
+ * "newBody"}`; a missing position or a different heading there answers 200 { ok: true, status:
236
+ * "gone" }. The body keeps its line structure. No wake and no inbox prompt: whichever tick picks
237
+ * the entry up refines it. */
238
+ export async function handleBacklogEdit(req, res, root) {
239
+ const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>", "newTitle": "...", "newBody": "..."}');
240
+ if (!body)
241
+ return;
242
+ const kind = checkedBacklogKind(res, body);
243
+ if (!kind)
244
+ return;
245
+ const pair = checkedEntryPositionAndTitle(res, body);
246
+ if (!pair)
247
+ return;
248
+ const { position, title } = pair;
249
+ const { newTitle, newBody } = body;
250
+ if (typeof newTitle !== "string" || collapseWhitespace(newTitle) === "") {
251
+ badRequest(res, "newTitle required");
252
+ return;
253
+ }
254
+ if (typeof newBody !== "string") {
255
+ badRequest(res, "newBody must be a string");
256
+ return;
257
+ }
258
+ const problem = bodyStructureProblem(newBody.replace(/\r\n?/g, "\n"));
259
+ if (problem !== null) {
260
+ badRequest(res, problem);
261
+ return;
262
+ }
263
+ const outcome = outcomeOrBadRequest(res, () => tryEditEntry(root, kind, position, title, newTitle, newBody));
264
+ if (outcome === null)
265
+ return;
266
+ if (outcome.status === "edited") {
267
+ const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Edit ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
268
+ sendJson(res, 200, { ok: true, status: "edited", file: outcome.file, title: outcome.title, committed });
269
+ }
270
+ else {
271
+ okGone(res);
272
+ }
273
+ }
@@ -75,7 +75,7 @@ export async function cmdGui(root, args, seams = {}) {
75
75
  if (allInterfaces) {
76
76
  // Name the concrete URLs teammates can open (token included, so they are openable
77
77
  // as printed), and say what exposure means: without a token the dashboard has no
78
- // auth and its prompt box steers the fleet; with one, the token is the gate.
78
+ // auth and its prompt box steers the team; with one, the token is the gate.
79
79
  for (const addr of lan())
80
80
  print(` also at http://${addr}:${port}${tokenSuffix}`);
81
81
  print(token