tumwater 0.2.0 → 0.3.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 (446) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +42 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +4 -3
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +402 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +1 -2
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +118 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +150 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,12 +1,13 @@
1
- // vocabulary: plain
2
1
  /** Shell completion derived from the help text. The spec is parsed from the same HELP stanzas
3
2
  * `tumwater help <command>` and the unknown-command suggestion use, so a new command or flag
4
3
  * completes the moment it gains a help stanza and can never drift from the listing. */
5
- import { customLoopNames, knownRoleIds, loadConfig } from "../config/config.js";
6
- import { AGENT_IDS, AGENT_KEYS, CLAUDE_AGENT_KEYS, MODEL_TIERS, ROLE_ENTRY_KEYS, TOP_LEVEL_KEYS } from "../config/config-schema.js";
7
- import { DOTTED_MAP_KEYS } from "../config/config-keys.js";
8
- import { HISTORY_RESULT_FLAG_NAMES } from "../history/history-data.js";
4
+ import { customRoleNames, knownRoleIds, loadConfig } from "../config/config.js";
5
+ import { BACKEND_IDS, BACKEND_KEYS, CLAUDE_AGENT_KEYS, CODEX_BACKEND_KEYS, MODEL_TIERS, ROLE_ENTRY_KEYS, THINKING_LEVELS, TOP_LEVEL_KEYS } from "../config/config-schema.js";
6
+ import { DOTTED_MAP_KEYS, DOTTED_SECTION_FIELDS } from "../config/config-keys.js";
7
+ import { compareCodeUnits } from "../collections.js";
8
+ import { HISTORY_MAX_RUNS, HISTORY_RESULT_FLAG_NAMES, HISTORY_SUMMARY_GROUPS, readRunRows } from "../history/history-data.js";
9
9
  import { templateIds } from "../init/init-templates.js";
10
+ import { DOCTOR_CHECK_NAMES } from "../doctor/doctor-check-names.js";
10
11
  import { allRoleIds } from "../roles/roles.js";
11
12
  import { EVENT_TYPE_LABELS } from "../events/event-labels.js";
12
13
  import { HELP, helpStanzas } from "./help.js";
@@ -14,7 +15,7 @@ export const SUPPORTED_SHELLS = ["bash", "zsh", "fish"];
14
15
  /** The completable words of a stanza's usage part: the text after `tumwater <command>` up to the
15
16
  * description column (two or more spaces). Bare lowercase words count inside `[...]` and as the
16
17
  * leading run directly after the command; outside brackets the first other token ends the run, so
17
- * a single-space description (`role <id> --clear-notes Clear that notebook`) adds nothing. */
18
+ * a single-space description (`role <id> --clear-notes Clear that notes`) adds nothing. */
18
19
  function usageWords(firstLine, command) {
19
20
  const rest = firstLine.replace(new RegExp(`^ {2}tumwater ${command}`), "");
20
21
  // Usage arguments follow the command after a single space; two or more spaces start the
@@ -66,7 +67,7 @@ export function completionSpec(help = HELP) {
66
67
  }
67
68
  return { commands, subcommands, flags };
68
69
  }
69
- /** The ids `--role` completes: this repo's loop ids, or the built-in catalog when tumwater.json is
70
+ /** The ids `--role` completes: this repo's role ids, or the built-in catalog when tumwater.json is
70
71
  * unreadable (a broken config must not write errors into the operator's prompt). */
71
72
  export function completionRoleIds(root) {
72
73
  try {
@@ -76,14 +77,29 @@ export function completionRoleIds(root) {
76
77
  return allRoleIds();
77
78
  }
78
79
  }
80
+ /** The selectors `history --model` completes: the distinct models recent runs started on, in the
81
+ * order `history --summary --by model` lists them. An unreadable or absent log must not write
82
+ * errors into the operator's prompt, and outside a repo it prints nothing. */
83
+ export function completionModels(root) {
84
+ try {
85
+ const models = new Set();
86
+ for (const row of readRunRows(root, HISTORY_MAX_RUNS, null))
87
+ if (row.model !== null)
88
+ models.add(row.model);
89
+ return [...models].sort(compareCodeUnits);
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
79
95
  /** The keys `config get|set|unset` complete: every form parseConfigKey accepts, with the
80
- * role-scoped keys of this repo's base roles and custom loops (not instance ids like
96
+ * role-scoped keys of this repo's base roles and custom roles (not instance ids like
81
97
  * `feature-2`). A broken tumwater.json falls back to the built-in catalog rather than writing
82
98
  * errors into the prompt. */
83
99
  export function completionConfigKeys(root) {
84
100
  let roleIds;
85
101
  try {
86
- roleIds = [...allRoleIds(), ...customLoopNames(loadConfig(root))];
102
+ roleIds = [...allRoleIds(), ...customRoleNames(loadConfig(root))];
87
103
  }
88
104
  catch {
89
105
  roleIds = allRoleIds();
@@ -91,19 +107,64 @@ export function completionConfigKeys(root) {
91
107
  const keys = [
92
108
  ...TOP_LEVEL_KEYS,
93
109
  ...MODEL_TIERS.map((tier) => `model.${tier}`),
94
- ...AGENT_IDS.flatMap((id) => (id === "claude" ? CLAUDE_AGENT_KEYS : AGENT_KEYS).map((field) => `backends.${id}.${field}`)),
110
+ ...BACKEND_IDS.flatMap((id) => (id === "claude" ? CLAUDE_AGENT_KEYS : id === "codex" ? CODEX_BACKEND_KEYS : BACKEND_KEYS).map((field) => `backends.${id}.${field}`)),
95
111
  ...DOTTED_MAP_KEYS.flatMap((map) => roleIds.map((id) => `${map}.${id}`)),
112
+ ...Object.entries(DOTTED_SECTION_FIELDS).flatMap(([section, fields]) => fields.map((field) => `${section}.${field}`)),
96
113
  ...roleIds.flatMap((id) => ROLE_ENTRY_KEYS.map((field) => `roles.${id}.${field}`)),
97
114
  ];
98
115
  return [...new Set(keys)];
99
116
  }
117
+ /** The value vocabulary `config set <key> <value>` completes for one key, or [] when the
118
+ * field is free-form. The discrete-valued keys source the SAME closed constants the config
119
+ * validation accepts — BACKEND_IDS for a `.backend` field or the top-level `backend`,
120
+ * MODEL_TIERS for a `.model` field (the tier-name shorthand checkModelTripleField/review
121
+ * allow), THINKING_LEVELS for a `.thinking` field or the top-level `thinking`, and
122
+ * `true|false` for the always-boolean `.enabled` — so a domain changes the moment its
123
+ * constant does. `provider` (top-level, `roles.<id>.provider`, `review.provider`) is a
124
+ * free-form pi provider name, NOT a backend id — the ids validation rejects as the retired
125
+ * backend namespace on the default pi backend — so it completes nothing rather than offering
126
+ * ids that `set` would refuse. Number/free-text values (instances, minRunIntervalSeconds,
127
+ * maxDailyCostUsd, backends' bin/args, a full model id) are not worth a TAB either. The
128
+ * `config set` value-position arm in bashScript shells out here, so the vocabulary lives in
129
+ * exactly one place on every shell. */
130
+ export function configValueWords(key) {
131
+ const dot = key.lastIndexOf(".");
132
+ if (dot < 0) {
133
+ // A dot-less key is a whole top-level key (TOP_LEVEL_KEYS); `backend` and `thinking` are
134
+ // the two discrete-valued ones, the rest are free-form numbers/strings/objects.
135
+ switch (key) {
136
+ case "backend": return [...BACKEND_IDS];
137
+ case "thinking": return [...THINKING_LEVELS];
138
+ default: return [];
139
+ }
140
+ }
141
+ switch (key.slice(dot + 1)) {
142
+ case "enabled": return ["true", "false"];
143
+ case "backend": return [...BACKEND_IDS];
144
+ case "model": return [...MODEL_TIERS];
145
+ case "thinking": return [...THINKING_LEVELS];
146
+ default: return [];
147
+ }
148
+ }
149
+ /** Commands whose first positional (word 2) is a role id, so completion offers
150
+ * `tumwater completion --roles` there. */
151
+ const ROLE_POSITIONAL_COMMANDS = ["role", "history", "revert"];
100
152
  /** Flags whose value is a file path: completion offers file names after them. */
101
- const PATH_FLAGS = ["--file", "--attach", "--notes-file", "--instructions-file"];
153
+ const PATH_FLAGS = ["--file", "--attach", "--notes-file", "--instructions-file", "--path"];
102
154
  /** The event-type codes `logs --type` filters by: the keys of the labels map, so a new event
103
155
  * type completes the moment it gains a label. Sorted for a stable, alphabetical offer of a
104
156
  * vocabulary too large to recall offhand — the same reason `--role`, `--template`, and
105
157
  * `--result` complete their values. */
106
158
  const EVENT_TYPE_CODES = Object.keys(EVENT_TYPE_LABELS).sort();
159
+ /** The pi `--thinking` levels `role <id> --thinking <level>` accepts: the canonical
160
+ * THINKING_LEVELS set, Set spread preserving its insertion order (= the order the
161
+ * role-help and config-field-checks message list them), so `--thinking` completes its
162
+ * vocabulary the way `--type` and `--result` complete theirs. One source, so a new level
163
+ * completes the moment THINKING_LEVELS grows. */
164
+ const THINKING_LEVEL_WORDS = [...THINKING_LEVELS];
165
+ /** The doctor check names, each in plain double quotes, for the scripts to loop over. The names
166
+ * hold no `"`, `$`, backtick or `\`, so plain double quotes are exact; a test pins this. */
167
+ const DOCTOR_CHECK_WORDS = DOCTOR_CHECK_NAMES.map((n) => `"${n}"`).join(" ");
107
168
  function bashScript(help) {
108
169
  const spec = completionSpec(help);
109
170
  const arms = spec.commands.map((command) => {
@@ -125,12 +186,32 @@ function bashScript(help) {
125
186
  });
126
187
  return [
127
188
  `# tumwater completion (generated from \`tumwater help\`)`,
189
+ `# Completes the item after the last comma of a comma-list value, keeping the items before it and dropping those already listed.`,
190
+ `_tumwater_list() {`,
191
+ ` local head="" word words=""`,
192
+ ` [[ "$2" == *,* ]] && head="\${2%,*},"`,
193
+ ` for word in $(printf '%s\\n' "$1"); do`,
194
+ ` [[ ",$head" == *",$word,"* ]] || words+="$word "`,
195
+ ` done`,
196
+ ` COMPREPLY=( $(compgen -P "$head" -W "$words" -- "\${2##*,}") )`,
197
+ `}`,
128
198
  `_tumwater() {`,
129
199
  ` local cur="\${COMP_WORDS[COMP_CWORD]}"`,
130
200
  ` local prev="\${COMP_WORDS[COMP_CWORD-1]}"`,
131
201
  ` COMPREPLY=()`,
132
- ` if [ "$prev" = "--role" ] || { [ "$COMP_CWORD" -eq 2 ] && [[ "\${COMP_WORDS[1]}" == role || "\${COMP_WORDS[1]}" == history ]] && [[ "$cur" != -* ]]; }; then`,
133
- ` COMPREPLY=( $(compgen -W "$(tumwater completion --roles 2>/dev/null)" -- "$cur") )`,
202
+ ` if [ "\${COMP_WORDS[1]}" = doctor ] && [ "$COMP_CWORD" -ge 2 ] && [[ "$cur" != -* ]]; then`,
203
+ ` local word="$cur" quote="" name`,
204
+ ` case "$word" in \\"*|\\'*) quote="\${word:0:1}"; word="\${word:1}";; esac`,
205
+ ` word="\${word//\\\\/}"`,
206
+ ` for name in ${DOCTOR_CHECK_WORDS}; do`,
207
+ ` if [[ "$name" == "$word"* ]]; then`,
208
+ ` if [ -n "$quote" ]; then COMPREPLY+=("$name"); else COMPREPLY+=("$(printf '%q' "$name")"); fi`,
209
+ ` fi`,
210
+ ` done`,
211
+ ` return 0`,
212
+ ` fi`,
213
+ ` if [ "$prev" = "--role" ] || { [ "$prev" = "--transcript" ] && [ "\${COMP_WORDS[1]}" = logs ]; } || { [ "$COMP_CWORD" -eq 2 ] && [[ ${ROLE_POSITIONAL_COMMANDS.map((c) => `"\${COMP_WORDS[1]}" == ${c}`).join(" || ")} ]] && [[ "$cur" != -* ]]; }; then`,
214
+ ` _tumwater_list "$(tumwater completion --roles 2>/dev/null)" "$cur"`,
134
215
  ` return 0`,
135
216
  ` fi`,
136
217
  ` case "$prev" in ${PATH_FLAGS.join("|")})`,
@@ -143,17 +224,44 @@ function bashScript(help) {
143
224
  ` return 0`,
144
225
  ` fi`,
145
226
  ` if [ "$prev" = "--result" ]; then`,
146
- ` COMPREPLY=( $(compgen -W "${Object.values(HISTORY_RESULT_FLAG_NAMES).join(" ")}" -- "$cur") )`,
227
+ ` _tumwater_list "${Object.values(HISTORY_RESULT_FLAG_NAMES).join(" ")}" "$cur"`,
147
228
  ` return 0`,
148
229
  ` fi`,
149
230
  ` if [ "$prev" = "--type" ]; then`,
150
- ` COMPREPLY=( $(compgen -W "${EVENT_TYPE_CODES.join(" ")}" -- "$cur") )`,
231
+ ` _tumwater_list "${EVENT_TYPE_CODES.join(" ")}" "$cur"`,
232
+ ` return 0`,
233
+ ` fi`,
234
+ ` if [ "$prev" = "--thinking" ]; then`,
235
+ ` COMPREPLY=( $(compgen -W "${THINKING_LEVEL_WORDS.join(" ")}" -- "$cur") )`,
236
+ ` return 0`,
237
+ ` fi`,
238
+ ` if [ "$prev" = "--by" ]; then`,
239
+ ` COMPREPLY=( $(compgen -W "${HISTORY_SUMMARY_GROUPS.join(" ")}" -- "$cur") )`,
240
+ ` return 0`,
241
+ ` fi`,
242
+ ` if [ "$prev" = "--model" ] && [ "\${COMP_WORDS[1]}" = role ]; then`,
243
+ ` COMPREPLY=( $(compgen -W "${MODEL_TIERS.join(" ")}" -- "$cur") )`,
244
+ ` return 0`,
245
+ ` fi`,
246
+ // bash splits at `:`; past a selector's colon `prev` is not --model, so nothing completes.
247
+ // A comma list, like --role, so it completes per item.
248
+ ` if [ "$prev" = "--model" ] && [ "\${COMP_WORDS[1]}" = history ]; then`,
249
+ ` _tumwater_list "$(tumwater completion --models 2>/dev/null)" "$cur"`,
151
250
  ` return 0`,
152
251
  ` fi`,
153
252
  ` if [ "$COMP_CWORD" -eq 3 ] && [ "\${COMP_WORDS[1]}" = config ] && [[ "\${COMP_WORDS[2]}" == get || "\${COMP_WORDS[2]}" == set || "\${COMP_WORDS[2]}" == unset ]] && [[ "$cur" != -* ]]; then`,
154
253
  ` COMPREPLY=( $(compgen -W "$(tumwater completion --config-keys 2>/dev/null)" -- "$cur") )`,
155
254
  ` return 0`,
156
255
  ` fi`,
256
+ // config set's VALUE sits at word 4, after the key a set write scopes (get/unset take no
257
+ // value, so only set reaches here). It shells out for the key's discrete vocabulary — the
258
+ // dotted fields (roles.<id>.<field>, review.<field>) and the top-level `backend`/`thinking`
259
+ // keys — and resolves to nothing for a free-form value, all decided in configValueWords so
260
+ // the vocabulary lives in exactly one place on every shell.
261
+ ` if [ "$COMP_CWORD" -eq 4 ] && [ "\${COMP_WORDS[1]}" = config ] && [ "\${COMP_WORDS[2]}" = set ] && [[ "$cur" != -* ]]; then`,
262
+ ` COMPREPLY=( $(compgen -W "$(tumwater completion --config-value "\${COMP_WORDS[3]}" 2>/dev/null)" -- "$cur") )`,
263
+ ` return 0`,
264
+ ` fi`,
157
265
  ` if [ "$COMP_CWORD" -eq 1 ]; then`,
158
266
  ` COMPREPLY=( $(compgen -W "${spec.commands.join(" ")}" -- "$cur") )`,
159
267
  ` return 0`,
@@ -167,18 +275,31 @@ function bashScript(help) {
167
275
  ``,
168
276
  ].join("\n");
169
277
  }
170
- /** The value suffix a fish flag line carries: what completes after the flag. */
171
- function fishFlagValue(flag) {
278
+ /** The value suffix a fish flag line carries: what completes after the flag. `--role`, `--result`
279
+ * and `--type` take comma lists, so they complete per item through `__fish_complete_list`,
280
+ * skipping the items already listed. */
281
+ function fishFlagValue(command, flag) {
172
282
  if (flag === "--role")
173
- return ` -x -a "(tumwater completion --roles 2>/dev/null)"`;
283
+ return ` -x -a "(__fish_complete_list , __tumwater_roles)"`;
284
+ if (flag === "--transcript" && command === "logs")
285
+ return ` -x -a "(__fish_complete_list , __tumwater_roles)"`;
174
286
  if (PATH_FLAGS.includes(flag))
175
287
  return ` -r -F`;
176
288
  if (flag === "--template")
177
289
  return ` -x -a "${templateIds().join(" ")}"`;
178
290
  if (flag === "--result")
179
- return ` -x -a "${Object.values(HISTORY_RESULT_FLAG_NAMES).join(" ")}"`;
291
+ return ` -x -a "(__fish_complete_list , __tumwater_results)"`;
180
292
  if (flag === "--type")
181
- return ` -x -a "${EVENT_TYPE_CODES.join(" ")}"`;
293
+ return ` -x -a "(__fish_complete_list , __tumwater_types)"`;
294
+ if (flag === "--thinking")
295
+ return ` -x -a "${THINKING_LEVEL_WORDS.join(" ")}"`;
296
+ if (flag === "--by")
297
+ return ` -x -a "${HISTORY_SUMMARY_GROUPS.join(" ")}"`;
298
+ // `role --model` completes the closed tier set; `history --model` completes the log's selectors.
299
+ if (flag === "--model" && command === "role")
300
+ return ` -x -a "${MODEL_TIERS.join(" ")}"`;
301
+ if (flag === "--model" && command === "history")
302
+ return ` -x -a "(__fish_complete_list , __tumwater_models)"`;
182
303
  return "";
183
304
  }
184
305
  function fishScript(help) {
@@ -187,6 +308,12 @@ function fishScript(help) {
187
308
  `# tumwater completion (generated from \`tumwater help\`)`,
188
309
  `complete -c tumwater -f`,
189
310
  `function __tumwater_argc; count (commandline -opc); end`,
311
+ `function __tumwater_doctor_checks; printf '%s\\n' ${DOCTOR_CHECK_WORDS}; end`,
312
+ `function __tumwater_unlisted; set -l typed (string split , -- (string match -r '^.*,' -- (commandline -ct))); while read -l word; contains -- $word $typed; or echo $word; end; end`,
313
+ `function __tumwater_roles; tumwater completion --roles 2>/dev/null | __tumwater_unlisted; end`,
314
+ `function __tumwater_results; printf '%s\\n' ${Object.values(HISTORY_RESULT_FLAG_NAMES).join(" ")} | __tumwater_unlisted; end`,
315
+ `function __tumwater_models; tumwater completion --models 2>/dev/null | __tumwater_unlisted; end`,
316
+ `function __tumwater_types; printf '%s\\n' ${EVENT_TYPE_CODES.join(" ")} | __tumwater_unlisted; end`,
190
317
  `complete -c tumwater -n __fish_use_subcommand -a "${spec.commands.join(" ")}"`,
191
318
  ];
192
319
  for (const command of spec.commands) {
@@ -194,9 +321,9 @@ function fishScript(help) {
194
321
  if (subs.length > 0)
195
322
  lines.push(`complete -c tumwater -n "__fish_seen_subcommand_from ${command}; and test (__tumwater_argc) -eq 2" -a "${subs.join(" ")}"`);
196
323
  for (const flag of spec.flags[command] ?? [])
197
- lines.push(`complete -c tumwater -n "__fish_seen_subcommand_from ${command}" -l ${flag.slice(2)}${fishFlagValue(flag)}`);
324
+ lines.push(`complete -c tumwater -n "__fish_seen_subcommand_from ${command}" -l ${flag.slice(2)}${fishFlagValue(command, flag)}`);
198
325
  }
199
- lines.push(`complete -c tumwater -n "__fish_seen_subcommand_from role history; and test (__tumwater_argc) -eq 2" -a "(tumwater completion --roles 2>/dev/null)"`, `complete -c tumwater -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from get set unset; and test (__tumwater_argc) -eq 3" -a "(tumwater completion --config-keys 2>/dev/null)"`, ``);
326
+ lines.push(`complete -c tumwater -n "__fish_seen_subcommand_from ${ROLE_POSITIONAL_COMMANDS.join(" ")}; and test (__tumwater_argc) -eq 2" -a "(tumwater completion --roles 2>/dev/null)"`, `complete -c tumwater -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from get set unset; and test (__tumwater_argc) -eq 3" -a "(tumwater completion --config-keys 2>/dev/null)"`, `complete -c tumwater -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set; and test (__tumwater_argc) -eq 4" -a "(tumwater completion --config-value (commandline -opc)[4] 2>/dev/null)"`, `complete -c tumwater -n "__fish_seen_subcommand_from doctor" -a "(__tumwater_doctor_checks)"`, ``);
200
327
  return lines.join("\n");
201
328
  }
202
329
  export function completionScript(shell, help = HELP) {
@@ -1,4 +1,3 @@
1
- // vocabulary: plain
2
1
  import { loadConfigSafe, knownRoleIds } from "../config/config.js";
3
2
  import { fail, say, sayJson, sayJsonLine } from "./cli-output.js";
4
3
  import { setConfigKey, unsetConfigKey, } from "../config/config-write.js";
@@ -9,10 +8,10 @@ import { unknownRoleMessage } from "../roles/roles.js";
9
8
  /** The `tumwater config` command's CLI layer (split out of operator/operator-commands.ts, which
10
9
  * keeps the other operator-command bodies): with no arguments, print the effective merged
11
10
  * config — exactly what `loadConfig(root)` returns — as pretty JSON, so an operator
12
- * debugging scheduling or custom-loop wiring sees what the team would actually load instead
11
+ * debugging scheduling or custom-role wiring sees what the team would actually load instead
13
12
  * of overlaying defaults onto tumwater.json by hand. A query, not a writer: no redaction (the
14
13
  * config holds no secrets — provider keys belong to pi's own env) and no transformation,
15
- * including the per-role entries custom loops merge into. `get <key>` prints that one key's
14
+ * including the per-role entries custom roles merge into. `get <key>` prints that one key's
16
15
  * resolved value (defaults merged in, exactly what the no-arg dump prints) as JSON, failing
17
16
  * with the valid top-level keys on an unknown one. An optional key nothing sets (model,
18
17
  * provider, fallbackModel, …) prints `null` — JSON's no-value — because JSON.stringify of an
@@ -28,7 +27,7 @@ import { unknownRoleMessage } from "../roles/roles.js";
28
27
  /** The config command's malformed-shapes usage line: cli.ts's arity gate fails a non-get/set
29
28
  * or wrongly-shaped subcommand with it, and cmdConfig repeats it as the defensive tail for an
30
29
  * argument the gate somehow missed. One constant so the synopsis cannot drift between the two
31
- * sites (mirrors tick-detail.ts's TICK_USAGE). */
30
+ * sites (mirrors run-detail.ts's RUN_DETAIL_USAGE). */
32
31
  export const CONFIG_USAGE = "usage: tumwater config [get <key> | set <key> <value> | unset <key>] (bare config prints the whole resolved config)";
33
32
  /** The key screens `config get` and `config unset` run before resolving or removing a value:
34
33
  * the shape parser's own error; the shared unknown-top-level-key message; then, for a dotted
@@ -64,22 +63,30 @@ function screenOperableKey(root, key) {
64
63
  }
65
64
  return { parsed, config };
66
65
  }
66
+ /** The resolved value of one screened key in a loaded config (defaults merged in): what `config
67
+ * get` prints and what `config unset` reports the key now resolves to. Undefined when nothing
68
+ * sets it. */
69
+ function resolvedConfigValue(config, parsed, key) {
70
+ const record = config;
71
+ return parsed.kind === "map"
72
+ ? record[parsed.map]?.[parsed.role]
73
+ : parsed.kind === "tier"
74
+ ? modelTierMap(record.model)[parsed.tier] // a string is shorthand for { default }
75
+ : parsed.kind === "agent"
76
+ ? record.backends?.[parsed.id]?.[parsed.field]
77
+ : parsed.kind === "section"
78
+ ? record[parsed.section]?.[parsed.field]
79
+ : parsed.kind === "role"
80
+ ? record.roles?.[parsed.id]?.[parsed.field]
81
+ : record[canonicalConfigKey(key)];
82
+ }
67
83
  /** The `tumwater config` command body: bare prints the resolved config; `get` and `unset`
68
84
  * screen their key through screenOperableKey, while `set` writes through setConfigKey. */
69
85
  export async function cmdConfig(root, args = []) {
70
86
  const [sub, key, ...rest] = args;
71
87
  if (sub === "get") {
72
88
  const { parsed, config } = screenOperableKey(root, key ?? "");
73
- const record = config;
74
- const value = parsed.kind === "map"
75
- ? record[parsed.map]?.[parsed.role]
76
- : parsed.kind === "tier"
77
- ? modelTierMap(record.model)[parsed.tier] // a string is shorthand for { default }
78
- : parsed.kind === "agent"
79
- ? record.backends?.[parsed.id]?.[parsed.field]
80
- : parsed.kind === "role"
81
- ? record.roles?.[parsed.id]?.[parsed.field]
82
- : record[canonicalConfigKey(key ?? "")];
89
+ const value = resolvedConfigValue(config, parsed, key ?? "");
83
90
  sayJsonLine(value === undefined ? null : value); // Absent optional key → JSON null.
84
91
  return;
85
92
  }
@@ -92,13 +99,22 @@ export async function cmdConfig(root, args = []) {
92
99
  // The same screens get runs first, so a typo'd top-level key, role id, or role field
93
100
  // fails identically — unsetConfigKey's own parse/field checks are the mutation-side
94
101
  // backstop, not the wording the operator sees.
95
- screenOperableKey(root, k);
102
+ const { parsed } = screenOperableKey(root, k);
96
103
  const result = unsetConfigKey(root, k);
97
104
  if (!result.ok)
98
105
  fail(result.error);
99
- say(result.oldValue === undefined
100
- ? `unset ${k}`
101
- : `unset ${k} (was ${JSON.stringify(result.oldValue)})`);
106
+ // The reload only feeds the message, so a failed one leaves `now` undefined.
107
+ const reloaded = loadConfigSafe(root).config;
108
+ const now = reloaded === undefined ? undefined : resolvedConfigValue(reloaded, parsed, k);
109
+ const resolves = now === undefined ? "" : JSON.stringify(now);
110
+ if (result.oldValue === undefined) {
111
+ say(`${k} is not set in tumwater.json, so nothing was removed` +
112
+ (now === undefined ? "" : ` (it resolves to ${resolves})`));
113
+ }
114
+ else {
115
+ const was = JSON.stringify(result.oldValue);
116
+ say(`unset ${k} (was ${was}${now === undefined ? "" : `; now resolves to ${resolves}`})`);
117
+ }
102
118
  return;
103
119
  }
104
120
  if (sub === "set") {
@@ -115,7 +131,14 @@ export async function cmdConfig(root, args = []) {
115
131
  const result = setConfigKey(root, key ?? "", raw);
116
132
  if (!result.ok)
117
133
  fail(result.error);
134
+ if (result.unchanged) {
135
+ say(`${canonicalConfigKey(key ?? "")} is already ${JSON.stringify(result.value)}, so tumwater.json was not written`);
136
+ return;
137
+ }
118
138
  say(`set ${canonicalConfigKey(key ?? "")} to ${JSON.stringify(result.value)}`);
139
+ const note = result.note;
140
+ if (note)
141
+ say(`note: ${note}`);
119
142
  // A bare `roles` set replaces the FILE's section, but the built-in catalog always merges
120
143
  // per entry at load, so the replacement cannot disable a role by omission. Say so here
121
144
  // rather than let the confirmation imply the role is gone.
@@ -0,0 +1,75 @@
1
+ /** The repaint loop every `-f/--follow` view shares (`status -f`, `queue -f`,
2
+ * `backlog -f`, `report -f`, `diff -f`): render once, then repaint on a fixed interval until
3
+ * Ctrl+C or the process that started the command exits. Lifted out of
4
+ * cli-query-commands.ts when `report -f` landed so the follow views have one leaf
5
+ * home instead of a per-command copy. */
6
+ import { startParentDeathWatch } from "../process/supervisor.js";
7
+ import { stripTerminalControls } from "../text/text.js";
8
+ /** The default repaint interval for a follow view — a fixed cadence, no knob. */
9
+ const FOLLOW_VIEW_INTERVAL_MS = 2000;
10
+ /** A follow view's live loop: render once, then repaint on an interval until Ctrl+C or the
11
+ * process that started this one is gone (the same parent-death rule `logs -f` uses).
12
+ * Repaints in place with the `watch` clear-screen shape on a TTY, and prints frames
13
+ * separated by a blank line when stdout is not a TTY so a piped follow stays readable.
14
+ * The frame text may be read synchronously or async (a `diff -f` frame re-reads the change
15
+ * by running git plumbing). A sync read paints synchronously — each interval tick paints
16
+ * exactly one frame, the deterministic shape the mock-clock follow tests (status, queue,
17
+ * backlog) depend on; an async read is guarded so a slow frame is not stacked by the ticks
18
+ * that arrive while it is still painting. */
19
+ export async function followView(frameText, seams) {
20
+ const tty = seams.tty ?? process.stdout.isTTY;
21
+ const interval = seams.intervalMs ?? FOLLOW_VIEW_INTERVAL_MS;
22
+ // The frame's text is rendered from model-written data (role live progress, run
23
+ // summaries, backlog titles) that can carry terminal escape sequences. The one-shot
24
+ // forms of these commands sanitize at say(); this live reprint writes raw to the
25
+ // terminal while rerendering its own clear-screen prefix, so it must strip controls
26
+ // itself — the display-boundary rule stripTerminalControls documents, applied here.
27
+ const paint = (text) => {
28
+ const clean = stripTerminalControls(text);
29
+ process.stdout.write(tty ? "\x1b[H\x1b[2J" + clean + "\n" : clean + "\n\n");
30
+ };
31
+ // First paint: render once before the repaint loop's interval is registered, awaiting a
32
+ // slow async read so the first frame is on screen before the loop begins. Guarded exactly
33
+ // like the loop below (a failed frame is skipped, not fatal): the first read of a follow
34
+ // view can strike the same transient failure (a git read, a snapshot whose file is
35
+ // momentarily unreadable) as any later tick, and killing the whole interactive follow on
36
+ // it — while the identical failure a tick later is skipped and retried — is the same fault
37
+ // with a different outcome based only on which tick it lands on. `Promise.resolve()`
38
+ // first so a synchronous frame-throw is caught too.
39
+ await Promise.resolve()
40
+ .then(frameText)
41
+ .then(paint)
42
+ .catch(() => { });
43
+ // Repaint on the interval. `painting` guards an async read against stacking frames: while
44
+ // one async frame is in flight, a tick that arrives skips its repaint rather than queueing
45
+ // a second write on top of the first. The guard is checked BEFORE the read, so a tick that
46
+ // lands mid-frame does not start another (git-spawning) read just to discard it — which
47
+ // would also leave that read's rejections unhandled. Sync callers never set `painting`, so
48
+ // the check is a no-op for them: each of their ticks still paints exactly one frame.
49
+ let painting = false;
50
+ await new Promise((resolve) => {
51
+ const timer = setInterval(() => {
52
+ if (painting)
53
+ return;
54
+ const result = frameText();
55
+ if (result instanceof Promise) {
56
+ painting = true;
57
+ void result
58
+ .then(paint)
59
+ .catch(() => { })
60
+ .finally(() => {
61
+ painting = false;
62
+ });
63
+ return;
64
+ }
65
+ paint(result);
66
+ }, interval);
67
+ startParentDeathWatch(() => {
68
+ clearInterval(timer);
69
+ resolve();
70
+ }, {
71
+ ...(seams.ppid ? { ppid: seams.ppid } : {}),
72
+ ...(seams.intervalMs !== undefined ? { intervalMs: seams.intervalMs } : {}),
73
+ });
74
+ });
75
+ }