tumwater 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +55 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +6 -4
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +404 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +65 -3
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +181 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +183 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -3,11 +3,21 @@
3
3
  * `## Open` to `## Answered` with a dated operator answer. The markdown surgery itself — the
4
4
  * fence-aware `### ` block cut and the `## Answered` skeleton — lives in src/backlog/
5
5
  * question-answer.ts (the shared QUESTIONS.md core the TUI and dashboard also call); this module
6
- * is only the command's say/fail/editor jacket over that core, imported by src/cli/
7
- * cli-query-commands.ts.
6
+ * is the command's argument scan, say/fail/editor jacket and commit-and-wake tail over that core,
7
+ * routed to by src/cli.ts.
8
8
  */
9
- import { fail, say, sayJson } from "./cli-output.js";
10
- import { openQuestionList, questionListPayload, UNCLOSED_FENCE_MESSAGE, tryAnswerQuestion } from "../backlog/question-answer.js";
9
+ import { fail, say, sayJson, warnIfUncommitted } from "./cli-output.js";
10
+ import { parseCountFlag, parsePositionFlag, rejectRepeatFlag } from "./cli-args.js";
11
+ import { JSON_FLAG, rejectEqualsForm } from "./cli-flag-specs.js";
12
+ import { refuseTeamWorkspace } from "./cli-repo-gate.js";
13
+ import { promptFileValue, readPromptFileArg } from "./cli-command-args.js";
14
+ import { editInEditor } from "./cli-editor.js";
15
+ import { gotSuffix, parsePositiveInt } from "../text/text.js";
16
+ import { typoSuffix } from "../text/suggest.js";
17
+ import { commitOperatorBacklogEdit } from "../backlog/backlog-commit.js";
18
+ import { stampedSubject } from "../git/commit-message.js";
19
+ import { wakeQuestionAsker } from "../operator/operator-intent.js";
20
+ import { answeredQuestionAt, answeredQuestionCount, answeredQuestionList, answeredQuestionPayload, openQuestionAt, openQuestionList, questionListPayload, renderShownQuestion, UNCLOSED_FENCE_MESSAGE, tryAnswerQuestion } from "../backlog/question-answer.js";
11
21
  import { bodyStructureProblem, normalizeEntryBody } from "../backlog/backlog-write.js";
12
22
  /** The `tumwater questions answer` entry point: `tryAnswerQuestion` without a title check, its
13
23
  * refusals mapped to `fail` (exit 1) with the CLI's wording. Returns the answered question's
@@ -32,7 +42,7 @@ function answeredOrFail(n, outcome) {
32
42
  /** `tumwater questions answer <n> --file <path>`: the decision's lines kept (normalizeEntryBody),
33
43
  * behind the same structural guard `plan --file` uses, so a stray `## ` line cannot grow a
34
44
  * phantom section. Refusals exit 1 through `fail`. */
35
- export function answerQuestionFromText(root, n, text, expectedTitle) {
45
+ function answerQuestionFromText(root, n, text, expectedTitle) {
36
46
  const body = normalizeEntryBody(text);
37
47
  if (body === "")
38
48
  fail("questions answer needs a decision: the file is blank");
@@ -45,12 +55,12 @@ export function answerQuestionFromText(root, n, text, expectedTitle) {
45
55
  export const ANSWER_EDITOR_CUT = "<!-- tumwater: write your decision above this line; this line and everything below it are dropped -->";
46
56
  /** The editor buffer for bare `questions answer <n>`: an empty decision area, the cut line, then
47
57
  * the question's heading and body for reference. */
48
- export function answerEditorBuffer(title, body) {
58
+ function answerEditorBuffer(title, body) {
49
59
  return ["", "", ANSWER_EDITOR_CUT, `### ${title}`, body].join("\n");
50
60
  }
51
61
  /** The decision an edited `answerEditorBuffer` holds: the text before the first cut line (all of
52
62
  * it when the operator deleted the line). */
53
- export function decisionFromEditorBuffer(text) {
63
+ function decisionFromEditorBuffer(text) {
54
64
  const lines = text.split("\n");
55
65
  const cut = lines.findIndex((l) => l.trim() === ANSWER_EDITOR_CUT);
56
66
  return (cut === -1 ? lines : lines.slice(0, cut)).join("\n");
@@ -66,9 +76,200 @@ export function sayAnswered(position, title, json, decision) {
66
76
  /** One entry point for the list modes so cli.ts stays a dispatcher: --json prints the payload
67
77
  * document (an empty outbox still prints it), prose prints the numbered lines or the empty
68
78
  * line. */
69
- export function sayQuestionList(root, json) {
79
+ function sayQuestionList(root, json, pattern) {
70
80
  if (json)
71
- sayJson(questionListPayload(root));
81
+ sayJson(questionListPayload(root, pattern));
72
82
  else
73
- say(openQuestionList(root));
83
+ say(openQuestionList(root, pattern));
84
+ }
85
+ /** `questions answer`'s `--file <path>` flag: the decision comes from the file, lines kept. */
86
+ const FILE_SPEC = { names: ["--file"], value: true, valueName: "<path>" };
87
+ /** `tumwater questions`: list open questions, or answer one with `answer <n> <decision>`. */
88
+ export async function cmdQuestions(root, args) {
89
+ // No requireReadyRepo gate, like backlog: the reader degrades to an empty list on a
90
+ // missing QUESTIONS.md, so the command inspects any directory instead of refusing (a team
91
+ // workspace still refuses).
92
+ // The answer form's decision is free-form prose, so peelPositionals cannot route the
93
+ // tokens: a decision word may begin with a single dash (`questions answer 1 "-50% spend
94
+ // cap"`), and peelPositionals would hand it to the flag gate as an unknown flag — the
95
+ // same masking parsePromptArgs avoids for prompt text by owning only `--`-prefixed
96
+ // tokens. So the questions parse scans args itself: a `--json` token before the
97
+ // question's number positional is the one flag (repeats refused, equals form named by
98
+ // rejectEqualsForm), any other `--`-prefixed token before it is refused as unknown, and
99
+ // every remaining token in order is prose — subcommand, position number, then the
100
+ // decision words. Past the number positional, tokens are decision words even when one
101
+ // is spelled `--json`: unquoted decision prose (`questions answer 1 keep --json output`)
102
+ // reaches the command as separate argv tokens, and a flag scan that matched `--json`
103
+ // anywhere silently ate the token out of the recorded decision and flipped the command
104
+ // into JSON mode. Unknown `--`-prefixed tokens stay refused even there, so a misspelled
105
+ // flag is still an error rather than silent decision text.
106
+ const words = [];
107
+ let jsonFlag = false;
108
+ let numbered = false;
109
+ let file;
110
+ let answeredFlag = false;
111
+ let limit;
112
+ let grep;
113
+ let show;
114
+ for (let i = 0; i < args.length; i++) {
115
+ const arg = args[i];
116
+ if (!numbered && arg === "--show") {
117
+ rejectRepeatFlag(show !== undefined, "--show");
118
+ show = parsePositionFlag("--show", args[++i], "--show 2");
119
+ continue;
120
+ }
121
+ if (!numbered && arg === "--answered") {
122
+ rejectRepeatFlag(answeredFlag, "--answered");
123
+ answeredFlag = true;
124
+ continue;
125
+ }
126
+ if (!numbered && arg === "-n") {
127
+ rejectRepeatFlag(limit !== undefined, "-n");
128
+ limit = parseCountFlag("-n", args[++i]);
129
+ continue;
130
+ }
131
+ if (!numbered && arg === "--grep") {
132
+ rejectRepeatFlag(grep !== undefined, "--grep");
133
+ const text = args[++i];
134
+ if (text === undefined || text === "")
135
+ fail("--grep needs text (usage: tumwater questions [--answered] --grep <text>)");
136
+ grep = text;
137
+ continue;
138
+ }
139
+ if (arg === "--file") {
140
+ rejectRepeatFlag(file !== undefined, "--file");
141
+ file = promptFileValue(args[++i]);
142
+ continue;
143
+ }
144
+ if (!numbered && arg === "--json") {
145
+ rejectRepeatFlag(jsonFlag, "--json");
146
+ jsonFlag = true;
147
+ continue;
148
+ }
149
+ if (arg.startsWith("--") && arg !== "--json") {
150
+ rejectEqualsForm(arg, [JSON_FLAG, FILE_SPEC, { names: ["--show"], value: true, valueName: "<n>" }, { names: ["--grep"], value: true, valueName: "<text>" }]);
151
+ // The complete flag set (this gate only sees `--`-flags, but `-n` is a real flag the
152
+ // list should not omit) plus the same did-you-mean hint the sibling commands'
153
+ // unknown-argument gate (cli-flag-specs's failUnknownArgument) attaches, so a mistyped
154
+ // `--ansered` names `--answered` instead of only printing the valid list — the missing
155
+ // treatment the questions gate was the one unknown-argument caller without.
156
+ const spellings = ["--json", "--file", "--answered", "--show", "--grep", "-n"];
157
+ fail(`unknown argument: ${arg} (valid flags for tumwater questions: ${spellings.join(", ")})${typoSuffix(arg, spellings)}`);
158
+ }
159
+ if (!numbered) {
160
+ // The position is a plain-decimal positive integer (parsePositiveInt's rule, the same
161
+ // one --cancel's position honors): bare Number() admitted hex ("0x2"), exponent
162
+ // ("1e2"), and whitespace-padded spellings as a valid question number, answering a
163
+ // question the operator never named.
164
+ if (parsePositiveInt(arg) !== null)
165
+ numbered = true;
166
+ }
167
+ words.push(arg);
168
+ }
169
+ if (limit !== undefined && !answeredFlag)
170
+ fail("-n needs --answered (it limits the answered questions listed)");
171
+ refuseTeamWorkspace(root);
172
+ if (show !== undefined) {
173
+ if (grep !== undefined)
174
+ fail("--grep does not apply to --show");
175
+ if (words.length > 0)
176
+ fail("--show prints one open question; it takes no subcommand");
177
+ if (file !== undefined)
178
+ fail("--file only applies to questions answer <n>");
179
+ if (answeredFlag) {
180
+ if (limit !== undefined)
181
+ fail("-n does not apply to --show");
182
+ const a = answeredQuestionAt(root, show);
183
+ if (a === null) {
184
+ const count = answeredQuestionCount(root);
185
+ const range = count > 0 ? ` — valid: 1-${count}` : "";
186
+ fail(`no answered question at position ${show} (${count} in QUESTIONS.md)${range}`);
187
+ }
188
+ if (jsonFlag)
189
+ sayJson(a);
190
+ else
191
+ say(renderShownQuestion(a));
192
+ return;
193
+ }
194
+ const q = openQuestionAt(root, show);
195
+ if (q === null) {
196
+ const count = questionListPayload(root).questions.length;
197
+ const range = count > 0 ? ` — valid: 1-${count}` : "";
198
+ fail(`no open question at position ${show} (${count} in QUESTIONS.md)${range}`);
199
+ }
200
+ if (jsonFlag)
201
+ sayJson(q);
202
+ else
203
+ say(renderShownQuestion(q));
204
+ return;
205
+ }
206
+ if (answeredFlag) {
207
+ if (words.length > 0)
208
+ fail("--answered lists answered questions; it takes no subcommand");
209
+ if (file !== undefined)
210
+ fail("--file only applies to questions answer <n>");
211
+ if (jsonFlag)
212
+ sayJson(answeredQuestionPayload(root, limit ?? 10, grep));
213
+ else
214
+ say(answeredQuestionList(root, limit ?? 10, grep));
215
+ return;
216
+ }
217
+ if (words.length === 0 && file !== undefined)
218
+ fail("--file only applies to questions answer <n>");
219
+ if (words.length === 0) {
220
+ sayQuestionList(root, jsonFlag, grep);
221
+ return;
222
+ }
223
+ if (grep !== undefined)
224
+ fail("--grep only applies to listing questions");
225
+ if (words[0] !== "answer")
226
+ fail(`unknown questions subcommand: ${words[0]} (use "answer <n> <decision>", or run \`tumwater questions\` with no subcommand to list them)${typoSuffix(words[0], ["answer"])}`);
227
+ // The offending token carries the same ` (got "...")` suffix every sibling number parser
228
+ // adds (parsePositiveInt's flag callers, --cancel); the bare `answer` form names none.
229
+ const raw = words.length > 1 ? words[1] : undefined;
230
+ const n = raw === undefined ? null : parsePositiveInt(raw);
231
+ if (n === null)
232
+ fail(`questions answer needs a positive question number: questions answer <n> "<decision>"${gotSuffix(raw)}`);
233
+ if (file !== undefined) {
234
+ if (words.length > 2)
235
+ fail(`unexpected argument ${JSON.stringify(words[2])} — with --file the decision comes from the file`);
236
+ const answered = answerQuestionFromText(root, n, readPromptFileArg(file));
237
+ await commitAndReportAnswer(root, n, jsonFlag, answered.title, answered.decision);
238
+ return;
239
+ }
240
+ const decision = words.slice(2).join(" ").trim();
241
+ if (words.length === 2) {
242
+ const open = questionListPayload(root).questions;
243
+ const question = open[n - 1];
244
+ if (question === undefined) {
245
+ const range = open.length > 0 ? ` — valid: 1-${open.length}` : "";
246
+ fail(`no question at position ${n} (${open.length} open)${range}`);
247
+ }
248
+ const edited = decisionFromEditorBuffer(editInEditor(answerEditorBuffer(question.title, question.body), `answer-${n}.md`, "pass a decision or --file <path>"));
249
+ if (edited.trim() === "")
250
+ fail("questions answer needs a decision: the editor left it blank; nothing changed");
251
+ const answered = answerQuestionFromText(root, n, edited, question.title);
252
+ await commitAndReportAnswer(root, n, jsonFlag, answered.title, answered.decision);
253
+ return;
254
+ }
255
+ if (decision === "")
256
+ fail('questions answer needs a decision: questions answer <n> "<decision>"');
257
+ // answerQuestion collapses the free-form decision to one line before writing it; report
258
+ // that written text in the --json payload so it matches the file, not the caller's spacing.
259
+ const { title, decision: writtenDecision } = answerQuestion(root, n, decision);
260
+ await commitAndReportAnswer(root, n, jsonFlag, title, writtenDecision);
261
+ }
262
+ /** Commit the answered question, report it in the requested output mode, and wake the
263
+ * asking role in prose mode — the tail every `questions answer` branch ends with. */
264
+ async function commitAndReportAnswer(root, n, jsonFlag, title, decision) {
265
+ warnIfUncommitted(await commitOperatorBacklogEdit(root, "QUESTIONS.md", stampedSubject("operator", `Answer question: ${title}`)), "QUESTIONS.md"); // vocabulary-ok: commit prefix stays
266
+ sayAnswered(n, title, jsonFlag, decision);
267
+ if (!jsonFlag)
268
+ sayAskerWake(root, title);
269
+ }
270
+ /** Prose-mode follow-up to `questions answer`: wake the asking role and print the confirmation. */
271
+ function sayAskerWake(root, title) {
272
+ const wake = wakeQuestionAsker(root, title);
273
+ if (wake !== null)
274
+ say(wake);
74
275
  }
package/dist/src/cli.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // vocabulary: plain
3
2
  /** The CLI's entry point and command dispatcher — the `bin` target package.json points at
4
3
  * (dist/src/cli.js). main() runs the Node-floor gate from package.json's `engines.node`, answers a
5
4
  * `--help` topic before any command parses arguments or gates on a ready repo, resolves the repo
@@ -13,33 +12,49 @@
13
12
  * modules; this file owns the shared sequence, including each case's flag rejection and its
14
13
  * ready-repo gate. An error escaping main() funnels through its catch to fail(errorMessage(err)),
15
14
  * the CLI's one error path. */
16
- import { parseCountFlag, } from "./cli/cli-args.js";
17
- import { grepFlagSpec, GUI_FLAG_SPECS, JSON_FLAG, LAST_FLAG, N_FLAG, rejectUnknownArgs, resultFlagSpec, ROLE_FLAG, RUN_FLAG_SPECS, sinceFlagSpec, STAT_FLAG, SUMMARY_FLAG, COUNT_FLAG, typeFlagSpec, DRY_RUN_FLAG, FORCE_FLAG, STOP_CANCEL_FLAG, STOP_IN_FLAG, REPROBE_FLAG, PROBLEMS_FLAG, } from "./cli/cli-flag-specs.js";
15
+ import { parseCountFlag, parseGrepFlag, parseModelFlag, parseResultFlag } from "./cli/cli-args.js";
16
+ import { grepFlagSpec, GUI_FLAG_SPECS, JSON_FLAG, LAST_FLAG, N_FLAG, rejectUnknownArgs, resultFlagSpec, modelFlagSpec, byFlagSpec, LOGS_TRANSCRIPT_FLAG, ROLE_FLAG, START_FLAG_SPECS, sinceFlagSpec, untilFlagSpec, STAT_FLAG, PATCH_FLAG, PROMPT_FLAG, TRANSCRIPT_FLAG, SUMMARY_FLAG, COUNT_FLAG, NOTABLE_FLAG, typeFlagSpec, DRY_RUN_FLAG, FORCE_FLAG, FOLLOW_FLAG, STOP_CANCEL_FLAG, STOP_IN_FLAG, REPROBE_FLAG, PROBLEMS_FLAG, KILL_ORPHANS_FLAG, COMMIT_BACKLOG_FLAG, RESYNC_INSTALL_FLAG, DROP_BROKEN_PINS_FLAG, RETIRE_LEFTOVERS_FLAG, } from "./cli/cli-flag-specs.js";
18
17
  import { fail, say, sayJsonOrRender } from "./cli/cli-output.js";
19
- import { historyTrailForm, parsePromptArgs, peelPositionals } from "./cli/cli-command-args.js";
20
- import { requireReadyRepo } from "./cli/cli-repo-gate.js";
18
+ import { historyTrailForm, parseDoctorChecks, parsePromptArgs, peelPositionals } from "./cli/cli-command-args.js";
19
+ import { refuseTeamWorkspace, requireReadyRepo } from "./cli/cli-repo-gate.js";
20
+ import { loadConfigSafe } from "./config/config.js";
21
21
  import { repoToplevel } from "./git/git.js";
22
22
  import { didYouMean } from "./text/suggest.js";
23
23
  import { errorMessage, gotSuffix, stripTerminalControls } from "./text/text.js";
24
24
  import { typoSuffix } from "./text/suggest.js";
25
- import { completionConfigKeys, completionRoleIds, completionScript, SUPPORTED_SHELLS } from "./cli/completion.js";
25
+ import { completionConfigKeys, completionModels, completionRoleIds, completionScript, configValueWords, SUPPORTED_SHELLS } from "./cli/completion.js";
26
26
  import { HELP, helpTopic, helpTopicForArgs, suggestCommand } from "./cli/help.js";
27
27
  import { PACKAGE_JSON, nodeFloorProblem, packageEnginesNode, packageVersion } from "./version.js";
28
28
  // The CLI's help text and its per-command topic parser live in help.ts — importing cli.ts
29
29
  // would run main(), so tests pin the topics against help.ts directly.
30
30
  /** One run's trail, shared by `history <role> <n>` and its hidden alias `tick` so the two cannot
31
31
  * drift. Positional-first: the <role> <n> tokens peel off and the remaining flags go through
32
- * rejectUnknownArgs (only --json, --last, --stat), so a listing flag such as --role is refused.
32
+ * rejectUnknownArgs (only --json, --last, --stat, --patch, --transcript, --prompt, --grep,
33
+ * --result, --model), so a listing flag such as --role is refused.
33
34
  * Arity is gated before the ready-repo check so a malformed call fails with its usage in any
34
- * directory; cmdTick owns the value validation and the --last/numbered rivals. */
35
+ * directory; cmdRunDetail owns the value validation and the --last/numbered rivals. */
35
36
  async function runTrailCommand(root, args) {
36
- const { cmdTick, TICK_USAGE } = await import("./tick/tick-detail.js");
37
- const { positionals, rest } = peelPositionals(args);
38
- rejectUnknownArgs("history", rest, [JSON_FLAG, LAST_FLAG, STAT_FLAG]);
39
- if (positionals.length < 1 || positionals.length > 2)
40
- fail(`usage: ${TICK_USAGE}`);
37
+ const { cmdRunDetail, RUN_DETAIL_USAGE } = await import("./run/run-detail.js");
38
+ const { HISTORY_GREP_VALUE_ERROR, HISTORY_RESULT_VALUE_ERROR, HISTORY_MODEL_VALUE_ERROR } = await import("./history/history.js");
39
+ // parseGrepFlag removes the pattern token, so `--grep two` cannot read "two" as a positional.
40
+ const { rest: ungrepped, pattern: grep } = parseGrepFlag(args, HISTORY_GREP_VALUE_ERROR);
41
+ const { rest: unresulted, raw: result } = parseResultFlag(ungrepped, HISTORY_RESULT_VALUE_ERROR);
42
+ const { rest: unmodeled, raw: model } = parseModelFlag(unresulted, HISTORY_MODEL_VALUE_ERROR);
43
+ const { positionals, rest } = peelPositionals(unmodeled);
44
+ rejectUnknownArgs("history", rest, [JSON_FLAG, LAST_FLAG, STAT_FLAG, PATCH_FLAG, TRANSCRIPT_FLAG, PROMPT_FLAG, { names: ["--grep"] }, { names: ["--result"] }, { names: ["--model"] }]);
45
+ if ((positionals.length < 1 && !rest.includes("--last")) || positionals.length > 2)
46
+ fail(`usage: ${RUN_DETAIL_USAGE}`);
41
47
  await requireReadyRepo(root);
42
- await cmdTick(root, positionals, rest.includes("--json"), rest.includes("--last"), rest.includes("--stat"));
48
+ await cmdRunDetail(root, positionals, rest.includes("--json"), {
49
+ last: rest.includes("--last"),
50
+ stat: rest.includes("--stat"),
51
+ patch: rest.includes("--patch"),
52
+ transcript: rest.includes("--transcript"),
53
+ prompt: rest.includes("--prompt"),
54
+ grep,
55
+ result,
56
+ model,
57
+ });
43
58
  }
44
59
  async function main() {
45
60
  const [, , command, ...args] = process.argv;
@@ -72,16 +87,16 @@ async function main() {
72
87
  const root = (await repoToplevel(process.cwd())) ?? process.cwd();
73
88
  switch (command) {
74
89
  case "init":
75
- // Imported lazily: cli/cli-run.ts pulls the whole team-booting subgraph (orchestrator,
90
+ // Imported lazily: cli/cli-start.ts pulls the whole team-booting subgraph (orchestrator,
76
91
  // supervisor, redeploy, launch-services — the run path's machinery), and every other
77
92
  // command's spawn pays module compilation for it unless the dispatch defers that load
78
- // to the commands that actually boot or serve a team.
79
- await (await import("./cli/cli-run.js")).cmdInit(root, args);
93
+ // to the commands that actually boot or serve a team. cli-init.ts stays light for init.
94
+ await (await import("./cli/cli-init.js")).cmdInit(root, args);
80
95
  break;
81
96
  case "start":
82
97
  case "run":
83
- rejectUnknownArgs("start", args, RUN_FLAG_SPECS);
84
- await (await import("./cli/cli-run.js")).cmdRun(root, args);
98
+ rejectUnknownArgs("start", args, START_FLAG_SPECS);
99
+ await (await import("./cli/cli-start.js")).cmdStart(root, args);
85
100
  break;
86
101
  case "tui":
87
102
  rejectUnknownArgs("tui", args, []);
@@ -108,7 +123,10 @@ async function main() {
108
123
  // args.
109
124
  if (args.length > 0) {
110
125
  if (args[0] !== "get" && args[0] !== "set" && args[0] !== "unset")
111
- fail(CONFIG_USAGE);
126
+ // A near-miss subcommand word gets the same typoSuffix `completion <shell>` and
127
+ // `logs --type` give, so `config gte` reads "did you mean `get`?" while a far-off
128
+ // word (`show`) and a flag (`--anything`) stay hint-free.
129
+ fail(`${CONFIG_USAGE}${typoSuffix(args[0] ?? "", ["get", "set", "unset"])}`);
112
130
  if (args[0] === "get" && args.length !== 2)
113
131
  fail("usage: tumwater config get <key>");
114
132
  if (args[0] === "set" && args.length !== 3)
@@ -124,7 +142,8 @@ async function main() {
124
142
  }
125
143
  case "report": {
126
144
  // No requireReadyRepo gate: the report aggregates files that degrade to zeros when
127
- // missing, so it runs (and prints an all-zero window) in any directory.
145
+ // missing, so it runs (and prints an all-zero window) in any directory; the team-workspace
146
+ // refusal still applies.
128
147
  rejectUnknownArgs("report", args, [
129
148
  {
130
149
  names: ["--days"],
@@ -138,9 +157,12 @@ async function main() {
138
157
  },
139
158
  { names: ["--failures"] },
140
159
  sinceFlagSpec("report --since"),
160
+ untilFlagSpec("report --until"),
141
161
  JSON_FLAG,
142
162
  ROLE_FLAG,
163
+ FOLLOW_FLAG,
143
164
  ]);
165
+ refuseTeamWorkspace(root);
144
166
  // --since is handled before the day-shape reads: it is a rival shape (totals over a
145
167
  // trailing window vs a series over whole days), not a modifier of either.
146
168
  await (await import("./report/report.js")).cmdReport(root, args);
@@ -149,23 +171,95 @@ async function main() {
149
171
  case "doctor": {
150
172
  // No requireReadyRepo gate: doctor's job is to report WHY the environment isn't ready,
151
173
  // so it must run outside a git repo and print fail lines rather than throwing.
152
- rejectUnknownArgs("doctor", args, [JSON_FLAG, REPROBE_FLAG, PROBLEMS_FLAG]);
174
+ const { positionals: checkNames, rest: doctorFlags } = peelPositionals(args);
175
+ rejectUnknownArgs("doctor", doctorFlags, [JSON_FLAG, REPROBE_FLAG, PROBLEMS_FLAG, KILL_ORPHANS_FLAG, COMMIT_BACKLOG_FLAG, RESYNC_INSTALL_FLAG, DROP_BROKEN_PINS_FLAG, RETIRE_LEFTOVERS_FLAG]);
176
+ const only = parseDoctorChecks(checkNames);
153
177
  const { runDoctor } = await import("./doctor/doctor.js");
178
+ let orphanKill;
179
+ if (args.includes("--kill-orphans")) {
180
+ const { killOrphans, orphanKillLines } = await import("./doctor/doctor-orphans.js");
181
+ orphanKill = await killOrphans(root);
182
+ // The text form prints the kill before the report; --json keeps stdout one document.
183
+ if (!args.includes("--json")) {
184
+ for (const line of orphanKillLines(orphanKill))
185
+ say(line);
186
+ say("");
187
+ }
188
+ }
189
+ let backlogCommit;
190
+ if (args.includes("--commit-backlog")) {
191
+ const { commitUncommittedBacklog, backlogCommitLines } = await import("./doctor/doctor-backlog.js");
192
+ const { config } = loadConfigSafe(root); // the report's own config, so base resolution and the off-base skip match the `uncommitted backlog` row's
193
+ backlogCommit = await commitUncommittedBacklog(root, config);
194
+ // The text form prints the commits before the report; --json keeps stdout one document.
195
+ if (!args.includes("--json")) {
196
+ for (const line of backlogCommitLines(backlogCommit))
197
+ say(line);
198
+ say("");
199
+ }
200
+ }
201
+ let resyncInstall;
202
+ if (args.includes("--resync-install")) {
203
+ const { resyncRootInstall, rootInstallResyncLines } = await import("./build/dep-install.js");
204
+ resyncInstall = await resyncRootInstall(root);
205
+ // The text form prints the re-sync outcome before the report; --json keeps stdout one
206
+ // document, carrying the result as `resyncInstall` on the payload.
207
+ if (!args.includes("--json")) {
208
+ for (const line of rootInstallResyncLines(resyncInstall))
209
+ say(line);
210
+ say("");
211
+ }
212
+ }
213
+ let brokenPinDrop;
214
+ if (args.includes("--drop-broken-pins")) {
215
+ const { dropBrokenPins, brokenPinDropLines } = await import("./doctor/doctor-contention-checks.js");
216
+ brokenPinDrop = await dropBrokenPins(root);
217
+ // The text form prints the drop before the report; --json keeps stdout one document,
218
+ // carrying the result as `brokenPinDrop` on the payload.
219
+ if (!args.includes("--json")) {
220
+ const lines = brokenPinDropLines(brokenPinDrop);
221
+ for (const line of lines)
222
+ say(line);
223
+ if (lines.length > 0)
224
+ say("");
225
+ }
226
+ }
227
+ let leftoverRetire;
228
+ if (args.includes("--retire-leftovers")) {
229
+ const { retireLeftovers, leftoverRetireLines } = await import("./doctor/doctor-contention-checks.js");
230
+ const { config } = loadConfigSafe(root); // the row's own config, so the reap and the report agree on what is retirable
231
+ leftoverRetire = await retireLeftovers(root, config);
232
+ // The text form prints the retire lines before the report; --json keeps stdout one
233
+ // document, carrying the result as `leftoverRetire` on the payload.
234
+ if (!args.includes("--json")) {
235
+ const lines = leftoverRetireLines(leftoverRetire);
236
+ for (const line of lines)
237
+ say(line);
238
+ if (lines.length > 0)
239
+ say("");
240
+ }
241
+ }
154
242
  const { renderDoctor, renderDoctorProblems, problemsOnly } = await import("./doctor/doctor-render.js");
155
243
  // The terminal probes unclaimed-verified models by default (7-day cache); --reprobe
156
244
  // forces every claimed model. The TUI/GUI call runDoctor with the default "never", so a
157
245
  // refresh never spends a model call.
158
246
  const probeImages = args.includes("--reprobe") ? "all" : "missing";
159
- const report = await runDoctor(root, undefined, undefined, probeImages);
247
+ const report = await runDoctor(root, undefined, undefined, probeImages, only);
160
248
  // --json prints the collector's own payload (the DoctorReport object), not a re-parse of
161
249
  // the render — the `report --json` precedent. The exit-code contract below holds in both
162
250
  // forms: 1 when any check fails, 0 otherwise; warnings never fail the exit.
163
251
  // --problems keeps the full report for the render (it counts the hidden ok rows) and
164
252
  // hands --json the filtered one; the exit code below reads the full report either way.
253
+ // --kill-orphans adds the kill result to the JSON object as `orphanKill`.
254
+ const withKill = (payload) => (orphanKill === undefined ? payload : { ...payload, orphanKill });
255
+ const withBacklogCommit = (payload) => backlogCommit === undefined ? withKill(payload) : withKill({ ...payload, backlogCommit });
256
+ const withResyncInstall = (payload) => resyncInstall === undefined ? withBacklogCommit(payload) : withBacklogCommit({ ...payload, resyncInstall });
257
+ const withBrokenPinDrop = (payload) => brokenPinDrop === undefined ? withResyncInstall(payload) : withResyncInstall({ ...payload, brokenPinDrop });
258
+ const withLeftoverRetire = (payload) => leftoverRetire === undefined ? withBrokenPinDrop(payload) : withBrokenPinDrop({ ...payload, leftoverRetire });
165
259
  if (args.includes("--problems"))
166
- sayJsonOrRender(args, args.includes("--json") ? problemsOnly(report) : report, renderDoctorProblems);
260
+ sayJsonOrRender(args, args.includes("--json") ? withLeftoverRetire(problemsOnly(report)) : report, renderDoctorProblems);
167
261
  else
168
- sayJsonOrRender(args, report, renderDoctor);
262
+ sayJsonOrRender(args, args.includes("--json") ? withLeftoverRetire(report) : report, renderDoctor);
169
263
  if (report.checks.some((c) => c.level === "fail"))
170
264
  process.exitCode = 1; // Warnings never fail the exit.
171
265
  break;
@@ -173,15 +267,18 @@ async function main() {
173
267
  case "logs": {
174
268
  const { cmdLogs, GREP_VALUE_ERROR, TYPE_VALUE_ERROR } = await import("./cli/log-commands.js");
175
269
  rejectUnknownArgs("logs", args, [
176
- { names: ["-f", "--follow"] },
270
+ FOLLOW_FLAG,
177
271
  N_FLAG,
178
272
  sinceFlagSpec("logs --since"),
273
+ untilFlagSpec("logs --until"),
179
274
  grepFlagSpec(GREP_VALUE_ERROR),
180
275
  typeFlagSpec(TYPE_VALUE_ERROR),
181
276
  JSON_FLAG,
182
277
  COUNT_FLAG,
278
+ NOTABLE_FLAG,
183
279
  ROLE_FLAG,
184
- { names: ["--prompt"] },
280
+ LOGS_TRANSCRIPT_FLAG,
281
+ PROMPT_FLAG,
185
282
  ]);
186
283
  await requireReadyRepo(root);
187
284
  await cmdLogs(root, args);
@@ -193,16 +290,20 @@ async function main() {
193
290
  await (await import("./cli/notify-command.js")).cmdNotify(root, args);
194
291
  break;
195
292
  case "history": {
196
- const { cmdHistory, HISTORY_GREP_VALUE_ERROR, HISTORY_RESULT_VALUE_ERROR } = await import("./history/history.js");
293
+ const { cmdHistory, HISTORY_GREP_VALUE_ERROR, HISTORY_RESULT_VALUE_ERROR, HISTORY_MODEL_VALUE_ERROR, HISTORY_BY_VALUE_ERROR } = await import("./history/history.js");
197
294
  if (historyTrailForm(args)) {
198
295
  await runTrailCommand(root, args);
199
296
  break;
200
297
  }
201
298
  rejectUnknownArgs("history", args, [
202
299
  N_FLAG,
300
+ FOLLOW_FLAG,
203
301
  sinceFlagSpec("history --since"),
302
+ untilFlagSpec("history --until"),
204
303
  grepFlagSpec(HISTORY_GREP_VALUE_ERROR),
205
304
  resultFlagSpec(HISTORY_RESULT_VALUE_ERROR),
305
+ modelFlagSpec(HISTORY_MODEL_VALUE_ERROR),
306
+ byFlagSpec(HISTORY_BY_VALUE_ERROR),
206
307
  JSON_FLAG,
207
308
  ROLE_FLAG,
208
309
  SUMMARY_FLAG,
@@ -220,17 +321,25 @@ async function main() {
220
321
  case "queue":
221
322
  await (await import("./cli/cli-query-commands.js")).cmdQueue(root, args);
222
323
  break;
324
+ case "reviews":
325
+ await (await import("./cli/cli-review-commands.js")).cmdReviews(root, args);
326
+ break;
223
327
  case "backlog":
224
328
  await (await import("./cli/cli-query-commands.js")).cmdBacklog(root, args);
225
329
  break;
330
+ case "quarantine":
331
+ await (await import("./cli/cli-query-commands.js")).cmdQuarantine(root, args);
332
+ break;
226
333
  case "archive":
227
- rejectUnknownArgs("archive", args, []);
334
+ rejectUnknownArgs("archive", args, [JSON_FLAG]);
228
335
  await requireReadyRepo(root);
229
- await (await import("./backlog/backlog-cli.js")).cmdArchive(root);
336
+ await (await import("./backlog/backlog-cli.js")).cmdArchive(root, args);
230
337
  break;
231
338
  case "bug": {
232
- const { OPERATOR_FILE_KINDS, bugWakeText, cancelBug, moveEntryToTop, releaseEntry, editBug, editBugFromText, fileBug, fileBugFromText } = await import("./backlog/backlog-write.js");
233
- const { bugTitleOf, cancelAndAnnounce, releaseAndAnnounce, topAndAnnounce, editAndAnnounce, fileAndAnnounce, listAndAnnounce, showAndAnnounce } = await import("./backlog/backlog-cli.js");
339
+ const { OPERATOR_FILE_KINDS, bugWakeText, cancelBug, editBug, editBugFromText, fileBug, fileBugFromText } = await import("./backlog/backlog-write.js");
340
+ const { moveEntryToTop, releaseEntry } = await import("./backlog/backlog-release.js");
341
+ const { bugTitleOf, cancelAndAnnounce, releaseAndAnnounce, topAndAnnounce, editAndAnnounce, fileAndAnnounce } = await import("./backlog/backlog-cli.js");
342
+ const { listAndAnnounce, showAndAnnounce } = await import("./backlog/backlog-cli-view.js");
234
343
  const usage = 'tumwater bug "<symptom>"';
235
344
  if (args.includes("--show")) {
236
345
  showAndAnnounce(root, args, "bug");
@@ -260,8 +369,10 @@ async function main() {
260
369
  break;
261
370
  }
262
371
  case "plan": {
263
- const { OPERATOR_FILE_KINDS, cancelPlan, moveEntryToTop, releaseEntry, editPlan, editPlanFromText, filePlan, filePlanFromText, planWakeText } = await import("./backlog/backlog-write.js");
264
- const { cancelAndAnnounce, releaseAndAnnounce, topAndAnnounce, editAndAnnounce, fileAndAnnounce, listAndAnnounce, showAndAnnounce, planTitleOf } = await import("./backlog/backlog-cli.js");
372
+ const { OPERATOR_FILE_KINDS, cancelPlan, editPlan, editPlanFromText, filePlan, filePlanFromText, planWakeText } = await import("./backlog/backlog-write.js");
373
+ const { moveEntryToTop, releaseEntry } = await import("./backlog/backlog-release.js");
374
+ const { cancelAndAnnounce, releaseAndAnnounce, topAndAnnounce, editAndAnnounce, fileAndAnnounce, planTitleOf } = await import("./backlog/backlog-cli.js");
375
+ const { listAndAnnounce, showAndAnnounce } = await import("./backlog/backlog-cli-view.js");
265
376
  const usage = 'tumwater plan "<title>" [body...]';
266
377
  if (args.includes("--show")) {
267
378
  showAndAnnounce(root, args, "plan");
@@ -291,13 +402,13 @@ async function main() {
291
402
  break;
292
403
  }
293
404
  case "questions":
294
- await (await import("./cli/cli-query-commands.js")).cmdQuestions(root, args);
405
+ await (await import("./cli/question-commands.js")).cmdQuestions(root, args);
295
406
  break;
296
407
  case "brief":
297
408
  await (await import("./cli/brief-command.js")).cmdBrief(root, args);
298
409
  break;
299
410
  case "role":
300
- await (await import("./cli/cli-query-commands.js")).cmdRole(root, args);
411
+ await (await import("./cli/cli-role-command.js")).cmdRole(root, args);
301
412
  break;
302
413
  case "prompt":
303
414
  // Parse before the gate: prompt's positionals are free-form, so rejectUnknownArgs
@@ -336,8 +447,8 @@ async function main() {
336
447
  break;
337
448
  }
338
449
  case "revert": {
339
- // Acts immediately on the primary checkout under the primary-worktree lock; the single
340
- // positional (the commit to undo) is validated inside cmdRevert.
450
+ // Acts immediately on the primary checkout under the primary-worktree lock; the
451
+ // positionals (a sha, or a run) are validated inside cmdRevert.
341
452
  await requireReadyRepo(root);
342
453
  await (await import("./operator/operator-commands.js")).cmdRevert(root, args);
343
454
  break;
@@ -362,12 +473,29 @@ async function main() {
362
473
  say(key);
363
474
  break;
364
475
  }
476
+ if (args.length === 2 && args[0] === "--config-value") {
477
+ // The vocabulary `config set <key> <value>` completes for a discrete-valued key
478
+ // (boolean/backend/model/thinking); free-form keys print nothing, so a `set` on one
479
+ // completes nothing instead of offering a wrong vocabulary.
480
+ for (const word of configValueWords(args[1]))
481
+ say(word);
482
+ break;
483
+ }
484
+ if (args.length === 1 && args[0] === "--models") {
485
+ for (const m of completionModels(root))
486
+ say(m);
487
+ break;
488
+ }
365
489
  const shell = args[0];
366
- if (args.length !== 1 || !SUPPORTED_SHELLS.includes(shell))
367
- // A single near-miss shell word gets the same typoSuffix `logs --type`/`history --result`
368
- // give, so `completion zs` reads "did you mean `zsh`?" while a far-off value (`tcsh`) and
369
- // a wrong arity (none or several words, typed shown by gotSuffix) stay hint-free.
370
- fail(`completion takes one shell: bash, zsh, or fish${gotSuffix(args.length > 1 ? args[1] : shell)}${args.length === 1 ? typoSuffix(shell ?? "", SUPPORTED_SHELLS) : ""}`);
490
+ // A wrong word count names the count, not a bad shell. A single near-miss shell word gets
491
+ // the same typoSuffix `logs --type`/`history --result` give, so `completion zs` reads
492
+ // "did you mean `zsh`?" while a far-off value (`tcsh`) stays hint-free.
493
+ if (args.length === 0)
494
+ fail("completion needs a shell: bash, zsh, or fish");
495
+ if (args.length > 1)
496
+ fail(`completion takes one shell: bash, zsh, or fish (got ${args.length} words: ${args.join(" ")})`);
497
+ if (!SUPPORTED_SHELLS.includes(shell))
498
+ fail(`completion takes one shell: bash, zsh, or fish${gotSuffix(shell)}${typoSuffix(shell ?? "", SUPPORTED_SHELLS)}`);
371
499
  say(completionScript(shell));
372
500
  break;
373
501
  }
@@ -400,7 +528,7 @@ async function main() {
400
528
  const topic = helpTopic(name);
401
529
  if (topic === null) {
402
530
  const suggestion = suggestCommand(name);
403
- fail(`no help topic: ${name}${didYouMean(suggestion)} (try \`tumwater help\` for the full command list)`);
531
+ fail(`no help topic: ${JSON.stringify(name)}${didYouMean(suggestion)} (try \`tumwater help\` for the full command list)`);
404
532
  }
405
533
  say(topic);
406
534
  }
@@ -411,7 +539,7 @@ async function main() {
411
539
  }
412
540
  default: {
413
541
  const suggestion = suggestCommand(command);
414
- fail(`unknown command: ${command}${didYouMean(suggestion)} (try \`tumwater help\`)`);
542
+ fail(`unknown command: ${JSON.stringify(command)}${didYouMean(suggestion)} (try \`tumwater help\`)`);
415
543
  }
416
544
  }
417
545
  }