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,230 @@
1
+ // vocabulary: plain
2
+ import { bodyStructureProblem, fileEntryWithBody, OPERATOR_FILE_KINDS, splitEntryFile, tryEditEntry } from "../../backlog/backlog-write.js";
3
+ import { stripEntryStamp } from "../../backlog/backlog-structure.js";
4
+ import { readTextIfExists } from "../../files/files.js";
5
+ import { submitRolePromptAndWake } from "../../operator/operator-intent.js";
6
+ import { promptLengthProblem } from "../../inbox/inbox-submit.js";
7
+ import { collapseWhitespace } from "../../text/text.js";
8
+ /** The backlog pane's title editor (Ctrl+N new entry, Ctrl+E on a selected plan or bug) on the
9
+ * shared prompt line. It owns its target (`entryEditFor`), its plan↔bug flip, its saved-draft
10
+ * pair, its exit helper, and the fileEntryWithBody / tryEditEntry / commit arms, plus the
11
+ * position math that counts `plan`/`bug` labels. Mutually exclusive with the other editors via
12
+ * the session's held registry. */
13
+ export function createEntryEditor(session) {
14
+ let entryEditFor = null;
15
+ let entryEditSavedInput = "";
16
+ let entryEditSavedCursor = 0;
17
+ // The triple state of the entry editor's prompt line: false (the title field), true (the
18
+ // file-path field whose contents replace the whole entry), and the draft the toggle parks
19
+ // when it hands the line to a different field.
20
+ let entryBodyFile = false;
21
+ let entryBodySavedInput = "";
22
+ let entryBodySavedCursor = 0;
23
+ // Leave the backlog pane's title editor (Esc, a saved edit, or Ctrl+T): the same settle
24
+ // contract as exitAnswerMode. Any open file mode is dropped too, so the editor always lands
25
+ // back in the title state on the next Ctrl+E.
26
+ const exitEntryEdit = () => {
27
+ if (!entryEditFor)
28
+ return;
29
+ entryEditFor = null;
30
+ entryBodyFile = false;
31
+ session.held.entry = null;
32
+ session.restorePromptFrom(entryEditSavedInput, entryEditSavedCursor);
33
+ };
34
+ const handleKey = (_str, key, ctx) => {
35
+ if (key.ctrl && key.name === "n") {
36
+ // On the Backlog view Ctrl+N files a new entry: it opens the title editor with a null
37
+ // position (new plan), and pressed again inside that editor flips plan <-> bug.
38
+ if (entryEditFor !== null && entryEditFor.position === null) {
39
+ const other = entryEditFor.kind === "plan" ? "bug" : "plan";
40
+ entryEditFor = { ...entryEditFor, kind: other };
41
+ session.held.entry = entryEditFor;
42
+ session.flashMessage(`new ${other} — Enter to file, Ctrl+N switches to ${other === "plan" ? "bug" : "plan"}, Esc cancels`);
43
+ return true;
44
+ }
45
+ // A Ctrl+N on any other state (not the Backlog view's new-entry slot, or an editor
46
+ // already holding the line) is the notebook/change/trail cycle, a pane arm the outer
47
+ // dispatch handles — this factory returns false so it runs.
48
+ if (ctx.view === ctx.roleIds.length + 1 &&
49
+ !session.held.budget && !session.held.rolePromptFor && !session.held.search &&
50
+ !session.held.pause && !session.held.queued &&
51
+ !session.held.answer && !entryEditFor && !session.held.retire) {
52
+ session.settleDraft((t) => (entryEditSavedInput = t), (n) => (entryEditSavedCursor = n));
53
+ entryEditFor = { kind: "plan", position: null, title: "" };
54
+ session.held.entry = entryEditFor;
55
+ session.input = "";
56
+ session.cursor = 0;
57
+ session.flashMessage("new plan — Enter to file, Ctrl+N switches to bug, Esc cancels");
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ if (key.ctrl && key.name === "e" && entryEditFor !== null && entryEditFor.position !== null) {
63
+ // A second Ctrl+E inside the entry editor toggles the same prompt line into the file
64
+ // field, whose path's contents replace the whole entry (splitEntryFile's rule) through
65
+ // tryEditEntry — the terminal twin of `tumwater plan|bug --edit <n> --file <path>`. A
66
+ // third Ctrl+E returns to the title draft, parked in the saved pair; Esc cancels all.
67
+ entryBodyFile = !entryBodyFile;
68
+ if (entryBodyFile) {
69
+ entryBodySavedInput = session.input;
70
+ entryBodySavedCursor = session.cursor;
71
+ session.input = "";
72
+ session.cursor = 0;
73
+ session.flashMessage(`replace ${entryEditFor.kind} #${entryEditFor.position} from a file — Enter to save, Ctrl+E back to the title, Esc cancels`);
74
+ }
75
+ else {
76
+ session.input = entryBodySavedInput;
77
+ session.cursor = entryBodySavedCursor;
78
+ session.flashMessage(`editing ${entryEditFor.kind} #${entryEditFor.position} title — Enter to save, Ctrl+E to replace from a file, Esc cancels`);
79
+ }
80
+ return true;
81
+ }
82
+ if (key.ctrl && key.name === "e" &&
83
+ !session.held.budget && !session.held.rolePromptFor && !session.held.search &&
84
+ !session.held.pause && !session.held.queued &&
85
+ !session.held.answer && !entryEditFor && !session.held.retire &&
86
+ ctx.view === ctx.roleIds.length + 1 && ctx.selectedEntry !== null) {
87
+ // Ctrl+E opens the title editor on the selected plan or bug (mnemonic: **E**dit); Enter
88
+ // saves through tryEditEntry, the non-exiting core the dashboard drawer shares, keeping
89
+ // the body and stamp. The prompt line is single-line, so only the title is editable here.
90
+ try {
91
+ const entries = session.flat();
92
+ const sel = Math.min(ctx.selectedEntry, entries.length - 1);
93
+ const picked = entries[sel];
94
+ if (picked === undefined) {
95
+ // an emptied list has nothing to edit
96
+ }
97
+ else if (picked.label === "done" || picked.label === "fixed" || picked.label === "answered") {
98
+ session.flashMessage("a done plan, fixed bug or answered question is read-only");
99
+ }
100
+ else if (picked.label === "question") {
101
+ session.flashMessage("only a plan or open bug can be edited here");
102
+ }
103
+ else {
104
+ const kind = picked.label === "plan" ? "plan" : "bug";
105
+ const position = kind === "plan" ? sel + 1 : sel - entries.filter((e) => e.label === "plan").length + 1;
106
+ session.settleDraft((t) => (entryEditSavedInput = t), (n) => (entryEditSavedCursor = n));
107
+ entryEditFor = { kind, position, title: picked.title };
108
+ session.held.entry = entryEditFor;
109
+ session.input = stripEntryStamp(picked.title);
110
+ session.cursor = session.input.length;
111
+ session.flashMessage(`editing ${kind} #${position} — Enter to save, Esc to cancel`);
112
+ }
113
+ }
114
+ catch (err) {
115
+ session.flashError(err);
116
+ }
117
+ return true;
118
+ }
119
+ if (key.name === "escape" && entryEditFor) {
120
+ // Esc cancels the title editor, restoring the previous prompt text; nothing is written.
121
+ const filing = entryEditFor.position === null;
122
+ exitEntryEdit();
123
+ session.flashMessage(filing ? "new entry cancelled" : "edit cancelled");
124
+ return true;
125
+ }
126
+ if (key.name === "return" && entryEditFor) {
127
+ // Enter saves the new title through tryEditEntry with the body re-read now, so a
128
+ // concurrent body edit is not clobbered by a stale copy. The entry is matched by flat
129
+ // index and title; a mismatch is treated as moved without writing. A blank title and a
130
+ // throw flash and STAY in the editor so the text survives.
131
+ const target = entryEditFor;
132
+ // Shared tail of the title and file edit arms: re-read the entry by flat index and
133
+ // title, guard against a concurrent move (so a mismatch is treated as moved, not
134
+ // written), then settle the editor and commit through tryEditEntry. The only thing
135
+ // the two arms pass differently is the (title, body) pair taking the entry's place;
136
+ // an omitted body preserves the matched entry's own body (the title edit arm).
137
+ const applyEntryEdit = (newTitle, newBody) => {
138
+ try {
139
+ // Both call sites (the file arm's and the title arm's Entry-save paths) run only
140
+ // when `target.position` is non-null, so the position math never sees a null.
141
+ const position = target.position;
142
+ const entries = session.flat();
143
+ const plans = entries.filter((e) => e.label === "plan").length;
144
+ const entry = entries[target.kind === "plan" ? position - 1 : plans + position - 1];
145
+ const outcome = entry?.label === target.kind && entry.title === target.title
146
+ ? tryEditEntry(session.root, target.kind, position, target.title, newTitle, newBody ?? entry.body)
147
+ : { status: "moved", title: "" };
148
+ exitEntryEdit();
149
+ if (outcome.status === "edited") {
150
+ session.commitBacklogEdit(outcome.file, `Edit ${target.kind}: ${outcome.title}`);
151
+ session.flashMessage(`edited ${target.kind} #${target.position}: ${outcome.title}`);
152
+ }
153
+ else {
154
+ session.clearBacklogSelection();
155
+ session.flashMessage(`that ${target.kind} was already removed or moved — nothing edited`);
156
+ }
157
+ }
158
+ catch (err) {
159
+ session.flashError(err);
160
+ }
161
+ };
162
+ if (entryBodyFile && target.position !== null) {
163
+ // File mode: the line holds a path whose contents replace title and body through the
164
+ // same title-checked tryEditEntry core the title edit uses, keeping the entry's
165
+ // position and its (planned …)/(found by …) stamp. A missing file, an empty path, a
166
+ // file with no title line, and a structurally unsound body all flash and stay here.
167
+ const p = session.input.trim();
168
+ if (!p) {
169
+ session.flashMessage("a file path is required");
170
+ return true;
171
+ }
172
+ let text;
173
+ try {
174
+ text = readTextIfExists(p);
175
+ }
176
+ catch (err) {
177
+ session.flashError(err);
178
+ return true;
179
+ }
180
+ if (text === null) {
181
+ session.flashMessage(`no such file: ${p}`);
182
+ return true;
183
+ }
184
+ const { title: rawTitle, body } = splitEntryFile(text);
185
+ const newTitle = stripEntryStamp(rawTitle);
186
+ if (!newTitle) {
187
+ session.flashMessage("the file needs a title line");
188
+ return true;
189
+ }
190
+ const problem = bodyStructureProblem(body);
191
+ if (problem !== null) {
192
+ session.flashMessage(problem);
193
+ return true;
194
+ }
195
+ applyEntryEdit(newTitle, body);
196
+ return true;
197
+ }
198
+ if (!session.input.trim()) {
199
+ session.flashMessage("an entry needs a title");
200
+ }
201
+ else if (target.position === null) {
202
+ // New entry (Ctrl+N): file by title with an empty body and wake its loop, the same
203
+ // steps handleBacklogFile takes; a length problem or a throw stays in the editor.
204
+ try {
205
+ const { role, wakeText } = OPERATOR_FILE_KINDS[target.kind];
206
+ const problem = promptLengthProblem(wakeText(collapseWhitespace(session.input)), role);
207
+ if (problem) {
208
+ session.flashMessage(problem);
209
+ }
210
+ else {
211
+ const filed = fileEntryWithBody(session.root, target.kind, session.input, "");
212
+ session.commitBacklogEdit(filed.file, `File ${target.kind}: ${filed.title}`);
213
+ submitRolePromptAndWake(session.root, role, wakeText(filed.title));
214
+ exitEntryEdit();
215
+ session.flashMessage(`filed ${target.kind}: ${filed.title} — woke ${role}`);
216
+ }
217
+ }
218
+ catch (err) {
219
+ session.flashError(err);
220
+ }
221
+ }
222
+ else {
223
+ applyEntryEdit(session.input);
224
+ }
225
+ return true;
226
+ }
227
+ return false;
228
+ };
229
+ return { handleKey, exit: exitEntryEdit };
230
+ }
@@ -0,0 +1,122 @@
1
+ // vocabulary: plain
2
+ import { setConfigKey, unsetConfigKey } from "../../config/config-write.js";
3
+ import { checkMinRunInterval } from "../../config/config-keys.js";
4
+ import { baseRoleOf } from "../../roles/loop-ids.js";
5
+ import { rawRoleField } from "../../roles/role-view.js";
6
+ /** The Ctrl+S min-run-interval editor on the shared prompt line, the terminal twin of
7
+ * `tumwater role <id> --min-run-interval` and the dashboard role drawer's `Min run interval`
8
+ * row. It owns its mode flag, its saved-draft pair, and the role pinned when it opened, so a
9
+ * roster shift cannot redirect the write. Enter writes through the shared
10
+ * setConfigKey/unsetConfigKey family the CLI and the drawer use — so checkMinRunInterval
11
+ * (naming `roles.<base>.minRunIntervalSeconds`) and validateConfig own the value's truth — and
12
+ * Esc, Ctrl+S again, and Ctrl+T cancel by restoring the prompt draft. Unlike the quiet editor
13
+ * there is no director refusal: `tumwater role director --min-run-interval` is accepted, so the
14
+ * director edits too. Mutually exclusive with the other editors on the one prompt line via the
15
+ * session's held registry. */
16
+ export function createIntervalEditor(session) {
17
+ let intervalMode = false;
18
+ let savedInput = "";
19
+ let savedCursor = 0;
20
+ // The role pinned when the editor opened, so Enter writes the same role's override even if
21
+ // syncRoster shrinks or reorders the roster while the editor is open.
22
+ let lockedRole = null;
23
+ // Leave min-run-interval mode (Esc, Ctrl+S again, or Ctrl+T): restore the saved prompt text.
24
+ const exitIntervalMode = () => {
25
+ if (!intervalMode)
26
+ return;
27
+ intervalMode = false;
28
+ session.held.interval = false;
29
+ session.restorePromptFrom(savedInput, savedCursor);
30
+ lockedRole = null;
31
+ };
32
+ const handleKey = (_str, key, ctx) => {
33
+ if (key.ctrl && key.name === "s") {
34
+ // Ctrl+S edits the viewed role's own min-run-interval override on its Transcript view.
35
+ // Every non-transcript view keeps Ctrl+S — the guard below lets the key fall through to
36
+ // the pane arms (History's trailing-window cycle) unless the interval editor already
37
+ // holds the line, in which case Ctrl+S is the toggle-out arm. Entering pre-fills the
38
+ // override from the FILE's raw `roles.<base>.minRunIntervalSeconds` (empty when unset —
39
+ // empty means "clear it, fall back to the team-wide interval" on save) and saves the
40
+ // prompt text; leaving restores it. There is no director exemption: the director ticks
41
+ // on the same clock, so `--min-run-interval` is accepted for it too.
42
+ if (!intervalMode && !(ctx.view >= 1 && ctx.view <= ctx.roleIds.length))
43
+ return false;
44
+ const role = ctx.roleIds[ctx.view - 1];
45
+ const held = session.heldEditorFlash("interval");
46
+ if (held) {
47
+ // Mutually exclusive with the other editors: all share one prompt line, so a mode may
48
+ // not enter while another holds it. Flash the way out first.
49
+ session.flashMessage(held);
50
+ return true;
51
+ }
52
+ if (intervalMode) {
53
+ exitIntervalMode();
54
+ return true;
55
+ }
56
+ // Pin the role first so the write below is safe against a mid-edit roster shift, then
57
+ // pre-fill from the file's own entry — one fresh read per open is the smallest honest
58
+ // source; a broken config degrades to no pre-fill and the write reports the error. The
59
+ // FILE (rawRoleField) is the source of truth, not the merged config: overlayDefaults
60
+ // seeds every built-in role's minRunIntervalSeconds from the catalog, so the merged
61
+ // value cannot tell an operator override from a seeded default, and clearing a role that
62
+ // only has the seed would otherwise be a no-op the operator could not see.
63
+ lockedRole = role;
64
+ const base = baseRoleOf(role);
65
+ const existing = rawRoleField(session.root, base, "minRunIntervalSeconds");
66
+ session.settleDraft((t) => (savedInput = t), (n) => (savedCursor = n));
67
+ intervalMode = true;
68
+ session.held.interval = true;
69
+ session.input = typeof existing === "number" ? String(existing) : "";
70
+ session.cursor = session.input.length;
71
+ session.flashMessage(`edit ${role}'s min run interval in seconds (0 allows back-to-back runs, empty clears): Enter to save, Esc to cancel`);
72
+ return true;
73
+ }
74
+ if (key.name === "escape" && intervalMode) {
75
+ // Esc cancels min-run-interval mode, restoring the previous prompt text.
76
+ exitIntervalMode();
77
+ return true;
78
+ }
79
+ if (key.name === "return" && intervalMode) {
80
+ // Enter commits the override through the shared setter family the CLI's
81
+ // `--min-run-interval` and the dashboard drawer use: an empty or whitespace-only line
82
+ // clears the key (`unsetConfigKey`), otherwise checkMinRunInterval (naming the dotted
83
+ // key) screens the value and `setConfigKey` writes `roles.<base>.minRunIntervalSeconds`
84
+ // as a JSON number. An invalid or rejected value flashes and STAYS in edit mode so the
85
+ // operator can fix it; success restores the saved prompt text.
86
+ const role = lockedRole;
87
+ const cfgKey = `roles.${baseRoleOf(role)}.minRunIntervalSeconds`;
88
+ const text = session.input.trim();
89
+ if (text === "") {
90
+ const result = unsetConfigKey(session.root, cfgKey);
91
+ if (result.ok) {
92
+ exitIntervalMode();
93
+ session.flashMessage(`${role}: min run interval cleared`);
94
+ }
95
+ else {
96
+ session.flashMessage(result.error); // broken config or write failure — stay in edit mode
97
+ }
98
+ return true;
99
+ }
100
+ const value = Number(text);
101
+ const invalid = checkMinRunInterval(value, cfgKey);
102
+ if (invalid) {
103
+ session.flashMessage(invalid); // name the rejected value and stay open
104
+ return true;
105
+ }
106
+ const result = setConfigKey(session.root, cfgKey, JSON.stringify(value));
107
+ if (result.ok) {
108
+ exitIntervalMode();
109
+ session.flashMessage(`${role}: min run interval set to ${text}s`);
110
+ }
111
+ else {
112
+ session.flashMessage(result.error); // broken config or write failure — stay in edit mode
113
+ }
114
+ return true;
115
+ }
116
+ return false;
117
+ };
118
+ return {
119
+ handleKey,
120
+ exit: exitIntervalMode,
121
+ };
122
+ }
@@ -0,0 +1,114 @@
1
+ // vocabulary: plain
2
+ import { saveRoleNote } from "../../agent/role-notes-write.js";
3
+ import { readTextIfExists } from "../../files/files.js";
4
+ import { roleNotesPath } from "../../paths.js";
5
+ /** The notes view's Ctrl+E replace-notes-from-a-file editor on the shared prompt line, the
6
+ * terminal twin of `tumwater role <id> --notes-file <path>` and the dashboard drawer's
7
+ * textarea. It owns its `notesEditFor` flag, its saved-draft pair, and the handleKey arms for
8
+ * its key (the toggle with the `heldEditorFlash(entering)` guard clause — the shared guard
9
+ * priority lives in the session module — plus Esc, Ctrl+E again, and Enter). Guarded to the
10
+ * notes view (`ctx.noteMode`), so the Report window's Ctrl+E arm keeps the key on its own
11
+ * views. The prompt line is single-line and a note may be multiline up to ROLE_NOTES_MAX_BYTES,
12
+ * so the editor takes a file path and reads it verbatim — exactly the CLI's own
13
+ * non-interactive replacement, the way the Activity view's Ctrl+V attach editor also takes a
14
+ * path. The write goes through saveRoleNote (the same write the CLI and the dashboard drawer
15
+ * make), so the 4 KB cap and its wording cannot drift; the fresh read is repainted through the
16
+ * session's refreshNotes seam. */
17
+ export function createNotesEditor(session) {
18
+ let notesEditFor = null;
19
+ let notesSavedInput = "";
20
+ let notesSavedCursor = 0;
21
+ // Leave the notes editor (Esc, Ctrl+E again, or Ctrl+T): restore the saved prompt text with
22
+ // no note written.
23
+ const exitNotesMode = () => {
24
+ if (notesEditFor === null)
25
+ return;
26
+ notesEditFor = null;
27
+ session.held.notes = null;
28
+ session.restorePromptFrom(notesSavedInput, notesSavedCursor);
29
+ };
30
+ const handleKey = (_str, key, ctx) => {
31
+ if (key.ctrl && key.name === "e" && ctx.noteMode &&
32
+ // The pane's noteMode closure goes stale when syncRoster shrinks the roster past the
33
+ // viewed role (it never resets the pane's flags), so the same in-range guard every
34
+ // transcript editor arm carries must hold here too before its view indexes roleIds.
35
+ ctx.roleIds.length > 0 && ctx.view >= 1 && ctx.view <= ctx.roleIds.length) {
36
+ // Ctrl+E toggles the replace-notes editor on the notes view's prompt line (mnemonic:
37
+ // **e**dit notes — the same key the Report window's window-cycle arm and the Queued
38
+ // pane's row-edit arm use on their own views, guarded away here by ctx.noteMode). The
39
+ // line edits one file path that becomes the role's whole note through the same
40
+ // saveRoleNote write the CLI and the dashboard drawer use. Entering saves the prompt
41
+ // draft and blanks the line; leaving restores it.
42
+ const held = session.heldEditorFlash("notes");
43
+ if (held) {
44
+ // Mutually exclusive with the other editors: all share one prompt line, so a mode may
45
+ // not enter while another holds it. Flash the way out instead of silently dropping
46
+ // the held mode's saved draft.
47
+ session.flashMessage(held);
48
+ return true;
49
+ }
50
+ if (notesEditFor !== null) {
51
+ exitNotesMode();
52
+ }
53
+ else {
54
+ // Mid-recall, hand the arrows' saved draft back before the notes editor takes the
55
+ // line — the same settle the role editors do — so leaving restores the
56
+ // draft, not a history entry, with no stale recall state riding along.
57
+ const role = ctx.roleIds[ctx.view - 1]; // defined: the open-arm range guard above pins the view into roleIds
58
+ session.settleDraft((t) => (notesSavedInput = t), (n) => (notesSavedCursor = n));
59
+ notesEditFor = role;
60
+ session.held.notes = role;
61
+ session.input = "";
62
+ session.cursor = 0;
63
+ session.flashMessage(`replace ${role}'s notes from a file — Enter to save, Esc to cancel`);
64
+ }
65
+ return true;
66
+ }
67
+ if (key.name === "escape" && notesEditFor !== null) {
68
+ // Esc cancels the notes editor, restoring the previous prompt text and writing nothing.
69
+ exitNotesMode();
70
+ session.flashMessage("notes edit cancelled");
71
+ return true;
72
+ }
73
+ if (key.name === "return" && notesEditFor !== null) {
74
+ // Enter reads the typed path whole and saves it through saveRoleNote — the shared write
75
+ // the CLI's `--notes-file` and the dashboard drawer make, so the 4 KB cap and its wording
76
+ // cannot drift. An empty path, a missing file, an unreadable file, and an over-cap note
77
+ // all flash and STAY in the editor so the operator can fix the path; an empty file
78
+ // clears the note. On success the notes view repaints the new note without leaving and
79
+ // re-entering, and the editor exits with the shared success wording.
80
+ const role = notesEditFor;
81
+ const p = session.input.trim();
82
+ if (p === "") {
83
+ session.flashMessage("a file path is required");
84
+ return true;
85
+ }
86
+ let text;
87
+ try {
88
+ text = readTextIfExists(p);
89
+ }
90
+ catch (err) {
91
+ session.flashError(err);
92
+ return true;
93
+ }
94
+ if (text === null) {
95
+ session.flashMessage(`no such file: ${p}`);
96
+ return true;
97
+ }
98
+ try {
99
+ const message = saveRoleNote(roleNotesPath(session.root, role), text);
100
+ session.refreshNotes(role);
101
+ exitNotesMode();
102
+ session.flashMessage(message);
103
+ }
104
+ catch (err) {
105
+ // The write or the 4 KB cap refused the note: flash the reason and stay in the editor
106
+ // so the operator can fix the path.
107
+ session.flashError(err);
108
+ }
109
+ return true;
110
+ }
111
+ return false;
112
+ };
113
+ return { handleKey, exit: exitNotesMode };
114
+ }