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
@@ -8,22 +8,26 @@ export function registerStartupMigration(step) {
8
8
  throw new Error(`startup migration "${step.name}" is already registered`);
9
9
  registry.push(step);
10
10
  }
11
- /** Run every registered step in registration order. A step that moved something logs one
12
- * `state_migrated` event; a step that throws logs one warning, the rest still run, and the failed
13
- * step retries at the next start. A second call moves and logs nothing. */
11
+ /** Run every registered step in registration order. A step that changed something logs one
12
+ * `state_migrated` event, whose detail also lists any collisions it kept; a step that only kept
13
+ * collisions logs nothing. A step that reports failed artifacts or throws logs one warning, the
14
+ * rest still run, and the step retries at the next start. A second call logs nothing once the
15
+ * first moved everything it could. */
14
16
  export async function runStartupMigrations(root) {
15
17
  for (const step of registry) {
16
18
  try {
17
- const notes = await step.run(root);
18
- if (notes.length > 0) {
19
+ const { moved, kept, failed } = await step.run(root);
20
+ if (moved.length > 0) {
19
21
  logEvent(root, {
20
22
  source: TUMWATER_SOURCE,
21
23
  type: "state_migrated",
22
24
  step: step.name,
23
- moved: notes.length,
24
- detail: notes.join("; "),
25
+ moved: moved.length,
26
+ detail: [...moved, ...kept].join("; "),
25
27
  });
26
28
  }
29
+ if (failed.length > 0)
30
+ warnEventBestEffort(root, TUMWATER_SOURCE, `startup migration ${step.name}: ${failed.join("; ")}`);
27
31
  }
28
32
  catch (err) {
29
33
  warnEventBestEffort(root, TUMWATER_SOURCE, `startup migration ${step.name} failed: ${errorMessage(err)}`);
package/dist/src/paths.js CHANGED
@@ -1,6 +1,6 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { DIRECTOR_ROLE } from "./roles/roles.js";
3
+ import { isDirectory } from "./files/files.js";
4
4
  /** All harness runtime state lives under <repo>/.tumwater (gitignored). */
5
5
  export const STATE_DIR = ".tumwater";
6
6
  /** The config file's basename — the name .gitignore's entry and the init output share. */
@@ -8,7 +8,7 @@ export const CONFIG_BASENAME = "tumwater.json";
8
8
  /** The tracked settings template's basename: the shareable baseline an untracked
9
9
  * tumwater.json is seeded from (plans/portability.md §4a/7). */
10
10
  export const EXAMPLE_CONFIG_BASENAME = "tumwater.example.json";
11
- /** The files the project brief (the managed initial prompt + status sections) may live in,
11
+ /** The files the project brief (the managed brief text + status sections) may live in,
12
12
  * candidates in resolution order: TUMWATER.md first, so an adopted repo's own README.md stays
13
13
  * untouched (plans/portability.md §7a/7); README.md is the compatibility path every repo
14
14
  * tumwater has created so far. */
@@ -23,7 +23,8 @@ export function configPath(root) {
23
23
  return path.join(root, CONFIG_BASENAME);
24
24
  }
25
25
  /** The cross-process lock serializing read-modify-write updates of tumwater.json
26
- * (writeConfigMutation and applyConfigRequest in config/config-write.ts): the CLI's
26
+ * (writeConfigMutation in config/config-write-core.ts and applyConfigRequest in
27
+ * config/config-request.ts): the CLI's
27
28
  * `tumwater config set`, a standalone `tumwater gui` server, and the running team are separate
28
29
  * processes that can rewrite one config in the same instant, and without serialization each
29
30
  * one's whole-file write silently drops the others' change. A lock directory owned by the
@@ -51,7 +52,7 @@ export function exampleConfigPath(root) {
51
52
  }
52
53
  /** The director's config-write request file, at its worktree root (plans/portability.md §3/7):
53
54
  * the director writes it, the harness consumes and deletes it before any commit path — so it
54
- * never enters a diff, a review prompt, or the project's history, and custom-loop management
55
+ * never enters a diff, a review prompt, or the project's history, and custom-role management
55
56
  * works whether or not tumwater.json is tracked. */
56
57
  export function configRequestPath(wt) {
57
58
  return path.join(wt, ".tumwater-config-request.json");
@@ -62,22 +63,46 @@ export function configRequestPath(wt) {
62
63
  export function worktreesDir(root) {
63
64
  return path.join(tumwaterDir(root), "worktrees");
64
65
  }
65
- /** A loop's persistent git worktree under .tumwater/worktrees/. */
66
+ /** Where a pooled slot's uncommitted leftovers are saved as patches before the slot is reset for
67
+ * another lease (slot-salvage.ts). A patch directly here is kept for the operator until session
68
+ * retention prunes it; one waiting for its role's next run lives under salvagePendingDir. */
69
+ export function salvageDir(root) {
70
+ return path.join(tumwaterDir(root), "salvage");
71
+ }
72
+ /** The patches of `role`'s unfinished work waiting for its next run to restore them
73
+ * (slot-salvage.ts, restoreSalvagedWork). */
74
+ export function salvagePendingDir(root, role) {
75
+ return path.join(salvageDir(root), "pending", role);
76
+ }
77
+ /** A role's persistent git worktree under .tumwater/worktrees/. */
66
78
  export function worktreePath(root, role) {
67
79
  return path.join(worktreesDir(root), role);
68
80
  }
69
- /** A loop's persistent branch name. */
81
+ /** A role's persistent branch name. */
70
82
  export function branchName(role) {
71
83
  return `tumwater/${role}`;
72
84
  }
73
- /** A role's per-loop state file (.tumwater/state/<role>.json; loop-state.ts). */
85
+ /** A role's state file (.tumwater/state/<role>.json; runner-state.ts). */
74
86
  export function statePath(root, role) {
75
87
  return path.join(tumwaterDir(root), "state", `${role}.json`);
76
88
  }
89
+ /** A backend's subscription account state (.tumwater/state/<backend>-account.json;
90
+ * subscription-account.ts): the billing answer and newest subscription window readings,
91
+ * written by the orchestrator only. */
92
+ export function accountPath(root, backend) {
93
+ return path.join(tumwaterDir(root), "state", `${backend}-account.json`);
94
+ }
77
95
  /** The claude account state (.tumwater/state/claude-account.json; claude-account.ts): the
78
- * billing answer and newest subscription window readings, written by the orchestrator only. */
96
+ * claude backend's subscription account — accountPath for the "claude" backend. */
79
97
  export function claudeAccountPath(root) {
80
- return path.join(tumwaterDir(root), "state", "claude-account.json");
98
+ return accountPath(root, "claude");
99
+ }
100
+ /** The codex write guard's trust cache (.tumwater/state/codex-hook-trust.json;
101
+ * codex-hook-trust.ts): the `{key, hash}` codex itself reports through `hooks/list`, keyed by
102
+ * codex version and hook definition so the lookup runs once per build and Codex version, not
103
+ * once per run. Runtime state under .tumwater/state/, never committed. */
104
+ export function codexHookTrustPath(root) {
105
+ return path.join(tumwaterDir(root), "state", "codex-hook-trust.json");
81
106
  }
82
107
  /** The worktree-use registry (plans/disk-floor.md, part 2/4; git/worktree-use.ts): which
83
108
  * worktrees are in use and when each was last used, so pressure reclaim can order candidates
@@ -103,15 +128,28 @@ export function orchestratorStatePath(root) {
103
128
  export function imageProbesPath(root) {
104
129
  return path.join(tumwaterDir(root), "state", "image-probes.json");
105
130
  }
106
- /** The `qa` flow-coverage ledger (tick/qa-coverage.ts): which flow each observer tick exercised
131
+ /** The images column `tumwater doctor` last read from `pi --list-models`, keyed provider/model.
132
+ * Runtime state under .tumwater/state/, never committed. */
133
+ export function piListedModelsPath(root) {
134
+ return path.join(tumwaterDir(root), "state", "pi-listed-models.json");
135
+ }
136
+ /** The `qa` flow-coverage ledger (run/qa-coverage.ts): which flow each observer run exercised
107
137
  * and how it went. Runtime state under .tumwater/state/, never committed — a passing check
108
138
  * must leave the repo untouched, or every cheap pass would move main and wake the team. */
109
139
  export function qaCoveragePath(root) {
110
140
  return path.join(tumwaterDir(root), "state", "qa-coverage.json");
111
141
  }
112
- /** A role's notebook (.tumwater/state/notes/<role>.md): the bounded note the role's own earlier
113
- * ticks wrote, carried into the next fresh session. Runtime state under .tumwater/state/, never
114
- * committed — the worktree resets to main each tick, so a committed note would move main. The
142
+ /** The `feedback` observer's per-install filing ledger (run/feedback-filings.ts): which GitHub
143
+ * issues this install already filed on znation/tumwater, so the next fresh session's prompt can
144
+ * render a "previously filed" block without re-deriving its dedupe from the network alone.
145
+ * Runtime state under .tumwater/state/, never committed — a filing must leave the repo untouched
146
+ * (the role's only write is `gh`), and the worktree resets to main each run. */
147
+ export function feedbackFilingsPath(root) {
148
+ return path.join(tumwaterDir(root), "state", "feedback-filings.json");
149
+ }
150
+ /** A role's notes (.tumwater/state/notes/<role>.md): the bounded note the role's own earlier
151
+ * runs wrote, carried into the next fresh session. Runtime state under .tumwater/state/, never
152
+ * committed — the worktree resets to main each run, so a committed note would move main. The
115
153
  * director has none: its work is the operator's prompt, not a recurring search. */
116
154
  export function roleNotesPath(root, role) {
117
155
  return path.join(tumwaterDir(root), "state", "notes", `${role}.md`);
@@ -123,6 +161,22 @@ export function roleNotesPath(root, role) {
123
161
  export function mergeStatePath(root) {
124
162
  return path.join(tumwaterDir(root), "state", "merging.json");
125
163
  }
164
+ /** The reverted changes still waiting for a free runner (.tumwater/state/reverted.json;
165
+ * src/merge/merge-drain.ts): `{ reverted: [{ commit, role, reasons }] }`, each entry dropped once
166
+ * assigned. */
167
+ export function revertedPendingPath(root) {
168
+ return path.join(tumwaterDir(root), "state", "reverted.json");
169
+ }
170
+ /** Merged commits awaiting their review after merging (src/review/post-merge-review.ts), for roles
171
+ * listed in `review.afterMerge`: `{ pending: PostReview[] }`. Runtime state, never committed. */
172
+ export function postReviewPath(root) {
173
+ return path.join(tumwaterDir(root), "state", "post-review.json");
174
+ }
175
+ /** The detached checkout a post-merge review reads the merged commit in; the leading underscore
176
+ * keeps it from colliding with a role worktree. */
177
+ export function postReviewWorktreePath(root) {
178
+ return path.join(worktreesDir(root), "_post-review");
179
+ }
126
180
  /** The merge marker's old file, deleted by the startup migration (the marker is display-only,
127
181
  * so a previous generation's is stale and is not read). */
128
182
  export function legacyLandingStatePath(root) {
@@ -140,13 +194,18 @@ export function autoRestartStampPath(root) {
140
194
  export function baselineVerdictsPath(root) {
141
195
  return path.join(tumwaterDir(root), "state", "baseline-verdicts.json");
142
196
  }
197
+ /** The quarantined flaky tests (review/known-flakes.ts): tests that failed and then passed on the
198
+ * same tree, newest 100, each with the BUGS.md entry filed for it. */
199
+ export function quarantinePath(root) {
200
+ return path.join(tumwaterDir(root), "state", "quarantine.json");
201
+ }
143
202
  /** Marker file `tumwater reset-counters` drops for a running team to consume (it must also
144
203
  * zero the runners' in-memory counters, or their next save resurrects the old values). */
145
204
  export function resetRequestPath(root) {
146
205
  return path.join(tumwaterDir(root), "reset-counters.json");
147
206
  }
148
207
  /** Marker file `tumwater wake [--role <id>]` drops for a running team to consume: like the
149
- * reset-counters marker it must name its targets because it can affect many loops, and like it
208
+ * reset-counters marker it must name its targets because it can affect many roles, and like it
150
209
  * it must reach the runners' IN-MEMORY schedule — the poll loop reads eligibility from the
151
210
  * in-memory state, so clearing only the on-disk file would not wake anything. */
152
211
  export function wakeRequestPath(root) {
@@ -172,9 +231,9 @@ export function reclaimRequestPath(root) {
172
231
  return path.join(tumwaterDir(root), "reclaim.json");
173
232
  }
174
233
  /** Per-role marker file `tumwater abort --role <id>` drops for a running team to consume:
175
- * kill that loop's in-flight tick. One file per role (presence = pending request; content is
234
+ * kill that role's in-flight run. One file per role (presence = pending request; content is
176
235
  * just `{ at }`) keeps consumption race-free and needs no parsing — unlike the single shared
177
- * reset-counters marker, which must name its targets because it affects many loops. */
236
+ * reset-counters marker, which must name its targets because it affects many roles. */
178
237
  export function abortRequestPath(root, role) {
179
238
  return path.join(tumwaterDir(root), `abort-${role}.json`);
180
239
  }
@@ -186,7 +245,7 @@ export function pausedPath(root) {
186
245
  return path.join(tumwaterDir(root), "paused.json");
187
246
  }
188
247
  /** Operator-intent marker for `tumwater stop --in <duration>`: `{ at, until }` in epoch ms. The
189
- * running team's watch consumes it when `until` passes; a fresh `tumwater run` clears a
248
+ * running team's watch consumes it when `until` passes; a fresh `tumwater start` clears a
190
249
  * stale one. */
191
250
  export function scheduledStopPath(root) {
192
251
  return path.join(tumwaterDir(root), "stop-at.json");
@@ -216,29 +275,29 @@ export const EVENTS_LOG_BASENAME = "events.jsonl";
216
275
  export function eventsLogPath(root) {
217
276
  return path.join(tumwaterDir(root), "log", EVENTS_LOG_BASENAME);
218
277
  }
219
- /** The event log's one archived generation (src/files/files.ts rotateIfLarge renames the grown log
220
- * to events.jsonl.1); the windowed readers continue into it when the live log ends inside the
221
- * window (event-window.ts). One archive, by design: the rotation note tells the truth about
222
- * coverage either way, and the archive path belongs beside eventsLogPath so the pair cannot
223
- * drift apart. */
224
- export function eventsArchivePath(root) {
225
- return eventsLogPath(root) + ".1";
278
+ /** An archived generation of the event log (src/files/files.ts rotateIfLarge renames the grown
279
+ * log to events.jsonl.1 and shifts older generations down, up to EVENTS_KEEP of them); the
280
+ * windowed readers continue into them when the live log ends inside the window (event-window.ts).
281
+ * `generation` defaults to 1, the newest archive. The path lives beside eventsLogPath so the
282
+ * pair cannot drift apart. */
283
+ export function eventsArchivePath(root, generation = 1) {
284
+ return eventsLogPath(root) + "." + generation;
226
285
  }
227
286
  /** A role's raw pi transcript log (.tumwater/log/<role>.pi.jsonl); tailed by the GUI/TUI. */
228
287
  export function piLogPath(root, role) {
229
288
  return path.join(tumwaterDir(root), "log", `${role}.pi.jsonl`);
230
289
  }
231
- /** A role's backend-native stdout log (.tumwater/log/<role>.<agent>.native.jsonl), written
232
- * beside the canonical raw log and kept for debugging only: no reader folds it, and the agent
290
+ /** A role's backend-native stdout log (.tumwater/log/<role>.<backend>.native.jsonl), written
291
+ * beside the canonical raw log and kept for debugging only: no reader folds it, and the backend
233
292
  * is in the name because one role's runs may switch backends and the two dialects must not
234
293
  * mix. Written and rotated like the raw log (src/agent/run-agent.ts). */
235
- export function nativeLogPath(root, role, agent) {
236
- return path.join(tumwaterDir(root), "log", `${role}.${agent}.native.jsonl`);
294
+ export function nativeLogPath(root, role, backend) {
295
+ return path.join(tumwaterDir(root), "log", `${role}.${backend}.native.jsonl`);
237
296
  }
238
297
  /** Root of the full-output files the bundled pi extension writes for oversized tool
239
298
  * results (.tumwater/log/tool-output/<toolCallId>.log). Pruned by the same sessionRetentionDays
240
299
  * window as sessions (orchestrator.ts's two retention passes): a pointer to a tool result is
241
- * only useful while its tick is recent, and the files otherwise accumulate without bound. */
300
+ * only useful while its run is recent, and the files otherwise accumulate without bound. */
242
301
  export function toolOutputDir(root) {
243
302
  return path.join(tumwaterDir(root), "log", "tool-output");
244
303
  }
@@ -246,7 +305,7 @@ export function toolOutputDir(root) {
246
305
  export function sessionsRootDir(root) {
247
306
  return path.join(tumwaterDir(root), "sessions");
248
307
  }
249
- /** A role's own pi session dir; hasResumableSession looks here for an interrupted tick to
308
+ /** A role's own pi session dir; hasResumableSession looks here for an interrupted run to
250
309
  * resume. */
251
310
  export function sessionDir(root, role) {
252
311
  return path.join(sessionsRootDir(root), role);
@@ -259,14 +318,7 @@ export function sessionDir(root, role) {
259
318
  export function nearestTumwaterRoot(sessionDir) {
260
319
  let dir = sessionDir;
261
320
  for (let i = 0; i < 64; i += 1) {
262
- let tumwater;
263
- try {
264
- tumwater = fs.statSync(path.join(dir, ".tumwater"));
265
- }
266
- catch {
267
- tumwater = undefined;
268
- }
269
- if (tumwater?.isDirectory())
321
+ if (isDirectory(path.join(dir, ".tumwater")))
270
322
  return dir;
271
323
  const parent = path.dirname(dir);
272
324
  if (parent === dir)
@@ -275,6 +327,17 @@ export function nearestTumwaterRoot(sessionDir) {
275
327
  }
276
328
  return undefined;
277
329
  }
330
+ /** The team root of the workspace `dir` sits in — `dir` lies under `worktreesDir(root)` for the
331
+ * nearest `.tumwater` root — or undefined for a team root itself, a directory elsewhere under
332
+ * it, or a directory with no root above. A read-only lookup for messages, never used to decide
333
+ * where to write (same caution as teamRootOf). */
334
+ export function owningTeamRoot(dir) {
335
+ const root = nearestTumwaterRoot(dir);
336
+ if (root === undefined)
337
+ return undefined;
338
+ const rel = path.relative(worktreesDir(root), dir);
339
+ return rel !== "" && !path.isAbsolute(rel) && !rel.startsWith("..") ? root : undefined;
340
+ }
278
341
  /** The team root that owns `sessionDir`: the `.tumwater` directory that contains it. A run's
279
342
  * session dir is always `<root>/.tumwater/sessions/…`, so the first ancestor holding a
280
343
  * `.tumwater` directory is the root; opencodeDbPath reads back to that same `.tumwater` for
@@ -291,7 +354,7 @@ export function teamRootOf(sessionDir) {
291
354
  }
292
355
  /** The directory the notes shim lands in, under a team root: `<root>/.tumwater/bin`.
293
356
  * run-agent installs the `tumwater-notes` command there (src/agent/notes-shim.ts) and
294
- * run-marker's agentBaseEnv prepends it to every run's PATH, so a run on any backend finds
357
+ * run-marker's backendBaseEnv prepends it to every run's PATH, so a run on any backend finds
295
358
  * the command (plans/notes-command.md "Reaching it from a run"). */
296
359
  export function notesShimDir(root) {
297
360
  return path.join(tumwaterDir(root), "bin");
@@ -313,30 +376,30 @@ export function opencodeModelsPath(root) {
313
376
  }
314
377
  /** Session dir for a role's review-gate runs. Kept OUTSIDE the role's own session dir on
315
378
  * purpose: hasResumableSession (the resume-after-shutdown guard) must only ever see the
316
- * AUTHOR's sessions — a leftover reviewer session would make an interrupted tick "resume"
379
+ * AUTHOR's sessions — a leftover reviewer session would make an interrupted run "resume"
317
380
  * into a review context it never had. Old files are cleaned by the same age-based prune.
318
381
  */
319
382
  export function reviewSessionDir(root, role) {
320
383
  return path.join(sessionsRootDir(root), "_review", role);
321
384
  }
322
385
  /** The per-run child of a role's review session dir: one directory per review of one change,
323
- * keyed by the run's tick and pinned sha, so a follow-up turn — or a later attempt that
386
+ * keyed by the review run's number and pinned sha, so a follow-up turn — or a later attempt that
324
387
  * resumes this review — continues exactly that run's session rather than whichever review
325
388
  * wrote last into the shared parent. */
326
389
  export function reviewRunSessionDir(root, role, runKey) {
327
390
  return path.join(reviewSessionDir(root, role), runKey);
328
391
  }
329
- /** Queued human prompts awaiting pickup by a loop (inbox.ts). */
392
+ /** Queued human prompts awaiting pickup by a role (inbox.ts). */
330
393
  export function inboxDir(root) {
331
394
  return path.join(tumwaterDir(root), "inbox");
332
395
  }
333
- /** The queue directory one loop's prompts wait in (inbox.ts): the director keeps its historical
334
- * queue at the inbox root so existing inboxes stay readable, and every other loop gets a
335
- * per-role subdirectory — `tumwater prompt --role <id>` writes where only that loop reads. */
396
+ /** The queue directory one role's prompts wait in (inbox.ts): the director keeps its historical
397
+ * queue at the inbox root so existing inboxes stay readable, and every other role gets a
398
+ * per-role subdirectory — `tumwater prompt --role <id>` writes where only that role reads. */
336
399
  export function roleInboxDir(root, role) {
337
400
  return role === DIRECTOR_ROLE ? inboxDir(root) : path.join(inboxDir(root), role);
338
401
  }
339
- /** The durable merge queue (src/merge/merge-queue.ts): a changed tick's pinned commit waits here
402
+ /** The durable merge queue (src/merge/merge-queue.ts): a changed run's pinned commit waits here
340
403
  * as one JSON file for the orchestrator's merge pipeline (plans/merge-queue.md 3/5, land-queue
341
404
  * speed 2c). Like the inbox it is a directory of timestamped files —
342
405
  * a crash between enqueue and drop loses nothing, and `tumwater status` can read it without the
@@ -349,6 +412,10 @@ export function mergeQueueDir(root) {
349
412
  export function legacyLandQueueDir(root) {
350
413
  return path.join(tumwaterDir(root), "land-queue"); // vocabulary-ok: legacy path read from old layouts
351
414
  }
415
+ /** How long a merge-lock taker outside the merge queue waits for the lock: a merge holds it through
416
+ * its in-lock check, which can run several minutes. A dead holder is still broken early by
417
+ * `tryBreakStale`. */
418
+ export const MERGE_LOCK_WAIT_MS = 15 * 60_000;
352
419
  /** The lock dir serializing merges to main (merge-to-main.ts; doctor checks it). */
353
420
  export function mergeLockDir(root) {
354
421
  return path.join(tumwaterDir(root), "merge.lock");
@@ -376,7 +443,7 @@ export function witnessWorktreePath(root) {
376
443
  return path.join(worktreesDir(root), "_build");
377
444
  }
378
445
  /** One slot of the worktree pool (plans/worktree-pool.md): a fixed `.tumwater/worktrees/_slot-<n>`
379
- * checkout that ticks and verifies lease and switch commits in place, instead of one `_land-<role>`
446
+ * checkout that runs and verifies lease and switch commits in place, instead of one `_land-<role>`
380
447
  * per role. The leading underscore keeps it from colliding with a role worktree. */
381
448
  export function slotWorktreePath(root, n) {
382
449
  return path.join(worktreesDir(root), `_slot-${n}`);
@@ -402,7 +469,7 @@ export function slotsLockPath(root) {
402
469
  return path.join(tumwaterDir(root), "state", "slots.lock");
403
470
  }
404
471
  /** The ref pinning a role's committed-but-not yet merged sha (plans/merge-queue.md invariant 4):
405
- * written right after the tick's commit, before its branch resets to main, so the reset cannot
472
+ * written right after the run's commit, before its branch resets to main, so the reset cannot
406
473
  * orphan the work. Deleted on every terminal merge outcome; kept until re-merged otherwise. */
407
474
  export function mergeRefName(role) {
408
475
  return `refs/tumwater/merge/${role}`;
@@ -414,7 +481,7 @@ export function legacyLandingRefName(role) {
414
481
  return `refs/tumwater/landing/${role}`; // vocabulary-ok: legacy name read from old refs or commits
415
482
  }
416
483
  /** The ref naming a role's rejected change while a revision is due (plans/revise-rejected.md):
417
- * written at the gate's rejection, read by the author's next tick to re-apply the rejected diff,
484
+ * written at the gate's rejection, read by the author's next run to re-apply the rejected diff,
418
485
  * and deleted once the revision merges, is dropped, conflicts, or exhausts its rounds. Keeping
419
486
  * the head reachable here stops git gc from collecting the one commit the revision re-applies. */
420
487
  export function rejectedRefName(role) {
@@ -7,6 +7,7 @@
7
7
  * keeps a legitimately slow suite below it while a hang surfaces within one quiet window of
8
8
  * the default. Both stall surfaces (runPi's warning, the dashboard's flag) must apply the
9
9
  * same threshold or one cries wolf while the other stays silent. */
10
+ import { streamsToolOutput } from "../agent/backend-traits.js";
10
11
  export function bufferedCommandStallMs(stallMs) {
11
12
  return Math.max(stallMs * 2, 10 * 60_000);
12
13
  }
@@ -14,7 +15,7 @@ export function bufferedCommandStallMs(stallMs) {
14
15
  * silence carries no hang signal and the stall warning would be a false alarm. Two shapes do
15
16
  * this: a pipe (`npm test 2>&1 | tail -8`) holds every byte in the pipeline until the upstream
16
17
  * command exits, and a stdout redirect (`npm test > /tmp/out`) sends the bytes to a file
17
- * instead of pi's pipe. Both are shapes the tick prompt itself prescribes for verification
18
+ * instead of pi's pipe. Both are shapes the run prompt itself prescribes for verification
18
19
  * runs, so the stall detector must not read their silence as a stall. A bare `2>&1` is NOT
19
20
  * such a shape: it points stderr at stdout's destination — pi's live pipe — so output still
20
21
  * streams and a hang stays detectable. `2>` and `2>>` likewise leave or append only stderr.
@@ -50,3 +51,13 @@ export function commandBuffersOutput(command) {
50
51
  }
51
52
  return false;
52
53
  }
54
+ /** True when a tool call gives no silence signal, so it must be judged by the buffered
55
+ * wall-clock threshold instead: either the agent never streams tool output (claude and
56
+ * opencode, which send no content updates) or the command itself buffers its stdout
57
+ * (commandBuffersOutput). The two stall surfaces — pi-watchdogs.ts's runPi stall warning and
58
+ * progress-data.ts's in-flight stall flag — must agree on this or one surface cries wolf
59
+ * while the other stays silent (BUGS.md 2026-09-28); this composes streamsToolOutput with the
60
+ * command classifier so they cannot drift. */
61
+ export function callBuffersOutput(agent, command) {
62
+ return !streamsToolOutput(agent) || commandBuffersOutput(command);
63
+ }
@@ -1,4 +1,4 @@
1
- /** Building the pi command line for one tick — pure argv construction with no subprocess or
1
+ /** Building the pi command line for one run — pure argv construction with no subprocess or
2
2
  * file I/O. Split out of pi.ts; the child-process integration (runPi) now lives in
3
3
  * src/agent/run-agent.ts and the run's result types in src/pi/pi-run-result.ts, while
4
4
  * test/pi-args.test.ts pins how config and session choices shape the command line. */
@@ -9,8 +9,9 @@ import { fileURLToPath } from "node:url";
9
9
  * oversized tool results, context-shake then reclaims old bulky results at 70% of the window,
10
10
  * context-budget appends its context-usage note to the (already bounded) result that crosses a
11
11
  * threshold, strip-thinking drops the model's replayed thinking from each request on a
12
- * small-window chat-completions model, and trim-system-prompt drops pi's docs section from the
13
- * system prompt. Notes ride the `tumwater-notes` command on
12
+ * small-window chat-completions model, trim-system-prompt drops pi's docs section from the
13
+ * system prompt, and write-guard refuses an edit or write outside the run's worktree. Notes ride
14
+ * the `tumwater-notes` command on
14
15
  * every backend from part 2/4 on, so the role-notes extension is no longer bundled. */
15
16
  export function bundledExtensionPaths() {
16
17
  return [
@@ -19,6 +20,7 @@ export function bundledExtensionPaths() {
19
20
  "context-budget.js",
20
21
  "strip-thinking.js",
21
22
  "trim-system-prompt.js",
23
+ "write-guard.js",
22
24
  ].map((file) => fileURLToPath(new URL(`../pi-extension/${file}`, import.meta.url)));
23
25
  }
24
26
  /** pi 1.2.0's built-in extensions other than `mcp`, read from pi's own `dist/extensions/index.js`.
@@ -29,7 +31,7 @@ export function bundledExtensionPaths() {
29
31
  * otherwise unchanged. A pi bump that renames or adds a built-in is a visible diff here; doctor
30
32
  * prints the same names so a package meant for the team does not vanish silently. */
31
33
  export const PI_KEPT_BUILTINS = ["llama.cpp", "codemode", "tool-search"];
32
- /** Build the pi argv for one tick: the harness's own flags, the bundled extensions, then the
34
+ /** Build the pi argv for one run: the harness's own flags, the bundled extensions, then the
33
35
  * user's config.piArgs (so a user flag still wins). */
34
36
  export function piArgs(opts) {
35
37
  const { config } = opts;
@@ -4,11 +4,11 @@
4
4
  * stderr exit rules, and the model-fallback-clone warning — so src/agent/run-agent.ts stays
5
5
  * backend-neutral. The claude adapter registers alongside it in src/agent/run-agent.ts. */
6
6
  import { piArgs } from "./pi-args.js";
7
- import { resolveBackendBin } from "./pi-bin.js";
8
- import { agentBaseEnv, withNotesPath } from "../process/run-marker.js";
7
+ import { resolveBinForBackend } from "./pi-bin.js";
8
+ import { backendBaseEnv, withNotesPath } from "../process/run-marker.js";
9
9
  import { isPermanentConfigError, matchBackendFailure } from "./pi-failure.js";
10
10
  import { hasResumableSession, sessionResumable } from "../agent/session-ledger.js";
11
- /** pi crashing on malformed JSON, as Node's JSON.parse phrases it on pi's stderr — five ticks in
11
+ /** pi crashing on malformed JSON, as Node's JSON.parse phrases it on pi's stderr — five runs in
12
12
  * the first 18 days died this way (one of them 2 h 39 m of director work on a fresh steering
13
13
  * prompt), each traced to a torn chunk from the model server rather than to the session. Matched
14
14
  * against the child's stderr at exit; exported for tests. */
@@ -29,7 +29,7 @@ export const piBackend = {
29
29
  defaultBin: "pi",
30
30
  binEnv: "TUMWATER_PI_BIN",
31
31
  resolveBin(config) {
32
- return resolveBackendBin(config, this);
32
+ return resolveBinForBackend(config, this);
33
33
  },
34
34
  invocation(run) {
35
35
  // The prompt rides on stdin, never argv: pi --print reads a piped stdin to EOF as the first
@@ -43,7 +43,7 @@ export const piBackend = {
43
43
  continueSession: run.continueSession,
44
44
  agentBin: run.agentBin,
45
45
  });
46
- const env = withNotesPath(agentBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
46
+ const env = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
47
47
  return { args, stdin: run.prompt, env };
48
48
  },
49
49
  translator() {
@@ -63,7 +63,7 @@ export const piBackend = {
63
63
  if (info.killed)
64
64
  return {};
65
65
  // A nonzero exit whose stderr ends in a JSON.parse failure is pi dying on a torn server
66
- // chunk: transient, retryable with --continue (the loop decides).
66
+ // chunk: transient, retryable with --continue (the runner decides).
67
67
  if (info.code !== 0 && TRANSIENT_PI_CRASH.test(info.stderr))
68
68
  return { transientPiCrash: true };
69
69
  // A provider-wide backend failure printed on pi's stderr (a severed stream, the provider's