tumwater 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +42 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +4 -3
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +402 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +1 -2
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +118 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +150 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,30 +1,34 @@
1
1
  import { allRoleIds, customRole, DIRECTOR_ROLE, roleById, unknownRoleMessage } from "../roles/roles.js";
2
- import { baseRoleOf } from "../roles/loop-ids.js";
2
+ import { baseRoleOf } from "../roles/runner-ids.js";
3
3
  import { planBacklogTarget } from "../scheduling/claims.js";
4
4
  import { configForRole } from "../config/config-views.js";
5
5
  import { NOTHING_TO_DO } from "../verdict/reply-contract.js";
6
6
  import { dequeuePromptEntry, dequeueRolePromptEntry, peekPrompt, peekRolePrompt } from "../inbox/inbox.js";
7
7
  import { stripNotBeforeMarker } from "../inbox/prompt-not-before.js";
8
- import { readBrief } from "../brief/brief.js";
9
- import { buildDirectorPrompt, buildTickPrompt } from "../prompt/prompt.js";
8
+ import { briefFile } from "../brief/brief.js";
9
+ import { buildDirectorPrompt, buildRunPrompt } from "../prompt/prompt.js";
10
10
  import { readPrinciples } from "../prompt/principles.js";
11
11
  import { buildCutOffNote } from "../prompt/prompt-followup.js";
12
- import { buildAssignmentNote, buildConflictDiscardNote, buildHeldEntriesNote, buildMergedPrerequisitesNote, buildRejectedReviewNote, buildWaitingPartsNote } from "../prompt/gate-prompts.js";
12
+ import { buildAssignmentNote, buildConflictDiscardNote, buildHeldEntriesNote, buildMergedPrerequisitesNote, buildRejectedReviewNote, buildWaitingPartsNote } from "../prompt/gate-notes.js";
13
13
  import { eligibleEntries, mergedPrerequisiteRanges, waitingPartRanges } from "../backlog/backlog-eligibility.js";
14
- import { detectBuildCheck } from "../build/build-check-detect.js";
14
+ import { notesBacklogBlock } from "../prompt/notes-backlog.js";
15
+ import { donePlans, openBugEntries, plannedPlanEntries } from "../backlog/backlog.js";
16
+ import { detectBuildCheck, gateCommandOf } from "../build/build-check-detect.js";
15
17
  import { flakeCounts, KNOWN_FLAKE_WINDOW_MS } from "../review/known-flakes.js";
16
18
  import { readEventsSince } from "../events/event-window.js";
17
- import { telemetryDigest } from "./telemetry-digest.js";
19
+ import { selfcheckDigest } from "./selfcheck-digest.js";
18
20
  import { readQaCoverage, renderCoverageBlock } from "./qa-coverage.js";
21
+ import { readFeedbackFilings, renderFilingsBlock } from "./feedback-filings.js";
19
22
  import { roleNotesPath } from "../paths.js";
20
23
  import * as path from "node:path";
21
- import { pathTokens } from "./stage-check.js";
22
- import { readTextOrNull, statOrNull } from "../files/files.js";
24
+ import { pathTokens } from "../text/text.js";
25
+ import { isDirectory, readTextOrNull, statOrNull } from "../files/files.js";
23
26
  import { dayKey } from "../text/datetime.js";
24
- import { appendPromptNote } from "../text/format.js";
27
+ import { appendPromptNote, prependPromptNote } from "../text/format.js";
25
28
  import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
26
29
  import { truncateWithNote } from "../text/text.js";
27
- import { renderBacklogIndexBlock, renderBacklogStructureBlock } from "../backlog/backlog-structure.js";
30
+ import { renderBacklogStructureBlock } from "../backlog/backlog-structure.js";
31
+ import { renderBacklogIndexBlock } from "../backlog/backlog-index.js";
28
32
  /** The repo paths the entry at `file` lines `range` names that are absent from the checkout at
29
33
  * `root`: tokens whose first segment is a directory there but which do not exist themselves —
30
34
  * the self-check's top-level-directory rule, read from the working tree. */
@@ -35,19 +39,19 @@ function missingEntryPaths(root, file, range) {
35
39
  const entry = text.split("\n").slice(range.start - 1, range.end).join("\n");
36
40
  return pathTokens(entry).filter((p) => {
37
41
  const top = p.slice(0, p.indexOf("/"));
38
- return statOrNull(path.join(root, top))?.isDirectory() === true && statOrNull(path.join(root, p)) === null;
42
+ return isDirectory(path.join(root, top)) && statOrNull(path.join(root, p)) === null;
39
43
  });
40
44
  }
41
- /** Read a role's notebook through files.ts's readTextOrNull — the one read-and-swallow
45
+ /** Read a role's notes through files.ts's readTextOrNull — the one read-and-swallow
42
46
  * step — or undefined when it is missing or unreadable (an empty or whitespace-only note is
43
- * left for buildTickPrompt to omit). The degradation is deliberate: a torn or absent
44
- * runtime-state file must never fail a tick.
47
+ * left for buildRunPrompt to omit). The degradation is deliberate: a torn or absent
48
+ * runtime-state file must never fail a run.
45
49
  *
46
50
  * The note is bounded on read as well as on write: the write path rejects text over
47
51
  * ROLE_NOTES_MAX_BYTES, but the file lives in hand-editable runtime state, so an oversized
48
52
  * note (an old build, a manual edit) would otherwise ride into every one of this role's
49
- * prefill-heavy tick prompts uncapped. The same defensive backstop readPrinciples and the
50
- * initial-prompt reader apply to their hand-editable files. A UTF-8 character is never
53
+ * prefill-heavy run prompts uncapped. The same defensive backstop readPrinciples and the
54
+ * brief text reader apply to their hand-editable files. A UTF-8 character is never
51
55
  * fewer than one byte, so a note the tool accepted (≤ 4096 bytes) is always ≤ 4096 chars
52
56
  * and never trips this bound; only an over-cap file is cut, with truncateWithNote's visible
53
57
  * marker naming the loss. */
@@ -78,42 +82,47 @@ function flakyChecksNote(root) {
78
82
  `names one of these, that bug is still firing. When \`## Open\` holds no entries, a check ` +
79
83
  `excused 2 or more times is a confirmed latent bug: take it as this run's latent-bug candidate.`);
80
84
  }
81
- /** Assemble the prompt a loop's next tick runs on — the whole "what should this tick see"
82
- * concern, split out of loop.ts so the runner owns only lifecycle and the prompt owns only
83
- * content. Returns null when the loop has nothing to run this tick (a director with an empty
85
+ /** Assemble the prompt a role's next run runs on — the whole "what should this run see"
86
+ * concern, split out of runner.ts so the runner owns only lifecycle and the prompt owns only
87
+ * content. Returns null when the role has nothing to run this run (a director with an empty
84
88
  * inbox); `userPrompt` carries the dequeued user request — the director's, or a per-role one
85
89
  * queued by `tumwater prompt --role <id>` — back so the runner can record it as pending and
86
- * re-queue it if the tick leaves it unfulfilled; `userPromptFile` is the queue file it came
90
+ * re-queue it if the run leaves it unfulfilled; `userPromptFile` is the queue file it came
87
91
  * from, so that bookkeeping can clean the prompt's attachments by that file's stem. */
88
- export function assembleTickPrompt({ root, config, role, state, preview = false, agent, compact = false }) {
89
- // One read of the brief yields both the prompt it carries and the owning file's name
90
- // (brief.ts's readBrief): readInitialPrompt + briefFile were two full reads of the same
91
- // file on this per-tick path.
92
- const briefState = readBrief(root);
93
- const initialPrompt = briefState?.prompt ?? "";
92
+ export function assembleRunPrompt({ root, config, role, state, preview = false, backend }) {
94
93
  // The brief's owning file (TUMWATER.md first, README.md as the compatibility path —
95
94
  // plans/portability.md §7a/7), named in both prompt builders' rules instead of a hardcoded
96
95
  // README.md. "README.md" is the fallback for a repo with no marked file yet — the team
97
96
  // runs blind on prompts either way, so the name in the rules should still point somewhere.
98
- const brief = briefState?.file ?? "README.md";
99
- // The project's design principles ride along in every prompt — tick and director alike — so
100
- // all loops share one standard of taste. Empty when the repo has no PRINCIPLES.md.
97
+ const brief = briefFile(root) ?? "README.md";
98
+ // The project's design principles ride along in every prompt — run and director alike — so
99
+ // all roles share one standard of taste. Empty when the repo has no PRINCIPLES.md.
101
100
  const principles = readPrinciples(root);
102
101
  // The project's resolved check (plans/portability.md §6/7): the prompt names the actual
103
102
  // verify command instead of asserting npm. Detection is a handful of stat calls — a
104
- // per-tick recompute keeps a config edit live on the next tick.
103
+ // per-run recompute keeps a config edit live on the next run.
105
104
  const check = detectBuildCheck(root, config) ?? undefined;
106
- // The bounded actionable index every tick and director prompt carries, replacing the model's
107
- // per-tick `grep -n '^##'` map of the backlog files (BUGS.md 2026-10-06). One render serves
108
- // both branches; the worktree starts at main at tick start, so the ranges hold then.
105
+ // The bounded actionable index every run and director prompt carries, replacing the model's
106
+ // per-run `grep -n '^##'` map of the backlog files (BUGS.md 2026-10-06). One render serves
107
+ // both branches; the worktree starts at main at run start, so the ranges hold then.
109
108
  const backlogIndex = renderBacklogIndexBlock(root);
109
+ // A change rejected in review is the only cross-run memory of what was built and why it
110
+ // failed — every run starts a fresh session, so the full reasons (with the reviewed head and
111
+ // timestamp) ride along on the next prompt until the role's next reviewed change replaces
112
+ // them. buildRunPrompt renders it at the very top, before the role charter, so the author
113
+ // cannot miss it (plans/reviewer-as-role.md part 4/4). A revision run carries the reasons in
114
+ // buildRevisionNote instead (the rejected diff is already in the worktree), so the note is
115
+ // suppressed here exactly as it was before this part.
116
+ const rejectedReview = (state.lastReview?.verdict === "reject" || state.lastReview?.verdict === "reverted") && !state.revision
117
+ ? buildRejectedReviewNote(state.lastReview, role)
118
+ : undefined;
110
119
  let prompt;
111
120
  let userPrompt = null;
112
121
  let userPromptFile = null;
113
122
  if (role === DIRECTOR_ROLE) {
114
123
  // A deferred director prompt's `tumwater:not-before` marker line is plumbing
115
124
  // (src/inbox/prompt-not-before.ts),
116
- // not content — stripped here exactly like the per-role path below, so the director's tick
125
+ // not content — stripped here exactly like the per-role path below, so the director's run
117
126
  // prompt and the runner's re-queue both see the operator's text alone.
118
127
  const entry = preview ? null : dequeuePromptEntry(root);
119
128
  const dequeued = preview ? peekPrompt(root) : entry?.text ?? null;
@@ -121,42 +130,41 @@ export function assembleTickPrompt({ root, config, role, state, preview = false,
121
130
  return null;
122
131
  userPrompt = stripNotBeforeMarker(dequeued);
123
132
  userPromptFile = entry?.file ?? null;
124
- // The compact prompt leaves the brief block out: the director reads the brief file in full,
125
- // and that file carries the same text.
126
- prompt = buildDirectorPrompt(userPrompt, compact ? "" : initialPrompt, principles, check, brief, undefined, backlogIndex, agent ?? configForRole(config, DIRECTOR_ROLE).agent, compact, config.roles[DIRECTOR_ROLE]?.instructions);
133
+ prompt = buildDirectorPrompt(userPrompt, principles, check, brief, undefined, backlogIndex, backend ?? configForRole(config, DIRECTOR_ROLE).agent, config.roles[DIRECTOR_ROLE]?.instructions);
127
134
  }
128
135
  else {
129
- // The loop id keys its inbox and notebook; every lookup ABOUT the role — catalog charter,
136
+ // The role id keys its inbox and notes; every lookup ABOUT the role — catalog charter,
130
137
  // instructions, the qa/selfcheck/clean blocks — resolves through the base role, so
131
138
  // `feature-2` runs the feature charter and `roles.feature.instructions` while keeping its
132
139
  // own queue and notes (plans/parallel-work-instances.md).
133
140
  const base = baseRoleOf(role);
134
- // Catalog first, then user-defined loops (plans/user-defined-loops.md): a custom's task
141
+ // Catalog first, then user-defined roles (plans/user-defined-loops.md): a custom's task
135
142
  // is its entire find-something-to-do text and the title identifies it in the prompt.
136
143
  const custom = config.customRoles.find((c) => c.name === role);
137
144
  const resolved = roleById(role) ?? (custom ? customRole(custom.name, custom.task) : undefined);
138
145
  if (!resolved) {
139
146
  // The shared unknownRoleMessage (parseRoleFlag, the operator commands) names the valid
140
- // ids; this defensive path — a runner asked to tick a role no catalog entry or
147
+ // ids; this defensive path — a runner asked to run a role no catalog entry or
141
148
  // customRoles task can answer — says the same, so if it ever fires it reads as the
142
149
  // harness bug it is instead of a bare dead end.
143
150
  const validIds = [...allRoleIds(), ...config.customRoles.map((c) => c.name)];
144
151
  throw new Error(unknownRoleMessage(role, validIds));
145
152
  }
146
153
  // A queued per-role prompt is dequeued here, before the prompt is built, so its text rides
147
- // in the tick's prompt; PendingPrompt records it as pending and re-queues it on every
154
+ // in the run's prompt; PendingPrompt records it as pending and re-queues it on every
148
155
  // unfulfilled outcome — including a red-main gate block, which returns before any run.
149
156
  // A deferred prompt's `tumwater:not-before` marker line is plumbing
150
157
  // (src/inbox/prompt-not-before.ts), not
151
- // content: it is stripped at delivery, so the loop sees the operator's text alone and the
158
+ // content: it is stripped at delivery, so the role sees the operator's text alone and the
152
159
  // runner's re-queue writes clean text.
153
160
  const entry = preview ? null : dequeueRolePromptEntry(root, role);
154
161
  const dequeued = preview ? peekRolePrompt(root, role) : entry?.text ?? null;
155
162
  const request = dequeued === null ? null : stripNotBeforeMarker(dequeued);
156
- // The selfcheck role's evidence is the harness's own event log, one level outside this
157
- // worktree, so its evidence module renders it (telemetryDigest) and the tick injects it.
158
- const digest = base === "selfcheck" ? telemetryDigest(root) : undefined;
159
- // The `qa` observer's flow rotation needs a memory of what it last exercised; every tick
163
+ // The selfcheck and feedback observers' evidence is the harness's own event log, one level
164
+ // outside this worktree, so its evidence module renders it (selfcheckDigest) and the run
165
+ // injects it.
166
+ const digest = base === "selfcheck" || base === "feedback" ? selfcheckDigest(root) : undefined;
167
+ // The `qa` observer's flow rotation needs a memory of what it last exercised; every run
160
168
  // is a fresh session, and a passing cheap check leaves nothing in the repo. The ledger is
161
169
  // runtime state, and a missing or unreadable one degrades to no block
162
170
  // (plans/observer-roles.md 2/2).
@@ -169,23 +177,49 @@ export function assembleTickPrompt({ root, config, role, state, preview = false,
169
177
  coverage = undefined;
170
178
  }
171
179
  }
180
+ // The `feedback` observer's per-install filing ledger: what this install already filed as
181
+ // GitHub issues on znation/tumwater, so the fresh session's charter dedupes against a
182
+ // "previously filed" block instead of the network alone. Runtime state, and a missing or
183
+ // unreadable ledger degrades to no block (plans/feedback-role.md 2/2).
184
+ // The render returns undefined for an empty ledger, so the prompt is unchanged in the common
185
+ // first-run case.
186
+ let filings;
187
+ if (base === "feedback") {
188
+ try {
189
+ filings = renderFilingsBlock(readFeedbackFilings(root));
190
+ }
191
+ catch {
192
+ filings = undefined;
193
+ }
194
+ }
172
195
  // The clean role's deterministic backlog repair: stranded plan headings in the primary
173
196
  // checkout's PLANS.md (plans, part 3/4), rendered like the digest and coverage blocks.
174
197
  // An unreadable or clean file gives no block, so the prompt is unchanged in the common case.
175
198
  const backlogStructure = base === "clean" ? renderBacklogStructureBlock(root) : undefined;
176
- // The role's notebook (PLANS.md "Role notebook"): its own earlier ticks' note. A missing,
199
+ // The role's notes (PLANS.md "Role notebook"): its own earlier runs' note. A missing,
177
200
  // empty, or unreadable file yields undefined, so the block is omitted; the write
178
- // instruction in buildTickPrompt is present either way. The director branch above never
179
- // reads this path, so it carries no notebook.
201
+ // instruction in buildRunPrompt is present either way. The director branch above never
202
+ // reads this path, so it carries no notes.
180
203
  const notes = readRoleNote(root, role);
181
204
  // The note file's mtime is when the notes were last saved (writes replace the file), so the
182
205
  // run can weigh how stale they are.
183
206
  const savedStat = notes === undefined ? null : statOrNull(roleNotesPath(root, role));
184
207
  const notesSavedDay = savedStat ? dayKey(savedStat.mtimeMs) : undefined;
208
+ // The live state of each entry and series the notes name, so a stale note cannot keep a hold
209
+ // the backlog has lifted (notes-backlog.ts). The three readers are already stat-cached. A
210
+ // note naming nothing live yields undefined and no block — the common case, since most notes
211
+ // are about the code, not the backlog.
212
+ const notesBacklog = notes === undefined || notes.trim() === ""
213
+ ? undefined
214
+ : notesBacklogBlock(notes, {
215
+ planned: plannedPlanEntries(root),
216
+ openBugs: openBugEntries(root),
217
+ donePlanTitles: donePlans(root),
218
+ });
185
219
  // The plan charter's stop threshold is derived from config (plans/parallel-work-
186
- // instances.md "…keeping the plan loop ahead", part 5c/7). The catalog text stays a
220
+ // instances.md "…keeping the plan role ahead", part 5c/7). The catalog text stays a
187
221
  // config-free description of the rule; this note supplies the concrete eligible-plan count
188
- // so the plan loop stops only once every configured feature instance has a plan waiting.
222
+ // so the plan role stops only once every configured feature instance has a plan waiting.
189
223
  const planTargetNote = base === "plan"
190
224
  ? `This run's stop target: when PLANS.md \`## Planned\` already holds ` +
191
225
  `${planBacklogTarget(config)} or more eligible plans, end with ${NOTHING_TO_DO}. ` +
@@ -194,30 +228,31 @@ export function assembleTickPrompt({ root, config, role, state, preview = false,
194
228
  : undefined;
195
229
  const flakyChecks = base === "bugfix" ? flakyChecksNote(root) : undefined;
196
230
  // A run holding a claim with no queued request is assigned its one entry (the assignment
197
- // note below). In the compact prompt such a run reads that entry first, so the backlog index
198
- // — what an unassigned run chooses its task from — stays out. The brief block stays out of
199
- // every compact prompt: an unassigned run reads the brief file in full, which carries the
200
- // same text, and an assigned one reads it only when its entry needs it.
201
- const variant = compact ? { assigned: state.claim !== undefined && request === null } : undefined;
202
- prompt = buildTickPrompt({
231
+ // note below). Such a run reads that entry first, so the backlog index — what an unassigned
232
+ // run chooses its task from — stays out.
233
+ const assigned = state.claim !== undefined && request === null;
234
+ prompt = buildRunPrompt({
203
235
  role: resolved,
204
- initialPrompt: compact ? "" : initialPrompt,
205
236
  principles,
206
237
  digest,
207
238
  coverage,
239
+ filings,
208
240
  backlogStructure,
209
- backlogIndex: variant?.assigned ? undefined : backlogIndex,
241
+ backlogIndex: assigned ? undefined : backlogIndex,
210
242
  notes,
211
243
  notesSavedDay,
244
+ notesBacklog,
212
245
  planTargetNote,
213
246
  flakyChecks,
214
247
  extraInstructions: config.roles[base]?.instructions,
215
248
  check,
249
+ gateCommand: gateCommandOf(config),
216
250
  briefFile: brief,
217
- agent: agent ?? configForRole(config, role).agent,
218
- compact: variant,
251
+ agent: backend ?? configForRole(config, role).agent,
252
+ assigned,
253
+ rejectedReview,
219
254
  // A per-role prompt queued by `tumwater prompt --role <id>` rides as an explicit user
220
- // request block; its text is also the tick's userPrompt,
255
+ // request block; its text is also the run's userPrompt,
221
256
  // so the runner's pending-prompt machinery (re-queue on unfulfilled, clear on merge)
222
257
  // treats it exactly like the director's dequeued request.
223
258
  userRequest: request ?? undefined,
@@ -229,8 +264,8 @@ export function assembleTickPrompt({ root, config, role, state, preview = false,
229
264
  }
230
265
  // A work instance holding a claim is told which entry is its one task (plans/parallel-work-
231
266
  // instances.md "Claims", part 4/7) — the harness picked it, so the charter's choosing step
232
- // is replaced. A tick that dequeued a user request runs that request instead and keeps the
233
- // claim for the next non-request tick. The range is looked up by key in the current checkout,
267
+ // is replaced. A run that dequeued a user request runs that request instead and keeps the
268
+ // claim for the next non-request run. The range is looked up by key in the current checkout,
234
269
  // so it survives a moved section boundary.
235
270
  const claim = role === DIRECTOR_ROLE ? undefined : state.claim;
236
271
  if (claim && userPrompt === null) {
@@ -238,28 +273,28 @@ export function assembleTickPrompt({ root, config, role, state, preview = false,
238
273
  prompt = appendPromptNote(prompt, buildAssignmentNote(claim, range, range ? missingEntryPaths(root, claim.file, range) : []));
239
274
  const merged = claim.file === "PLANS.md" ? mergedPrerequisiteRanges(root, claim.title) : [];
240
275
  if (merged.length > 0)
241
- prompt = appendPromptNote(prompt, buildMergedPrerequisitesNote(merged, compact));
276
+ prompt = appendPromptNote(prompt, buildMergedPrerequisitesNote(merged));
242
277
  }
243
278
  if (claim && userPrompt === null && claim.file === "PLANS.md") {
244
279
  const waiting = waitingPartRanges(root, claim.title);
245
280
  if (waiting.length > 0)
246
- prompt = appendPromptNote(prompt, buildWaitingPartsNote(waiting, compact));
281
+ prompt = appendPromptNote(prompt, buildWaitingPartsNote(waiting));
247
282
  }
248
283
  if (!claim && userPrompt === null && role !== DIRECTOR_ROLE && state.heldElsewhere?.length) {
249
284
  prompt = appendPromptNote(prompt, buildHeldEntriesNote(state.heldElsewhere));
250
285
  }
251
- // A change rejected in review is the only cross-tick memory of what was built and why it
252
- // failed — every tick starts a fresh session, so the full reasons ride along on the next
253
- // prompt until the role's next reviewed change replaces them.
254
- if (state.lastReview?.verdict === "reject" && !state.revision) {
255
- prompt = appendPromptNote(prompt, buildRejectedReviewNote(state.lastReview, role));
286
+ // The director builds its prompt through buildDirectorPrompt, which does not take the rejected
287
+ // note, so the standing rejection leads its prompt here instead (a director-authored change can
288
+ // be rejected too). Kept uniform with the authoring roles: the reasons open the run.
289
+ if (rejectedReview !== undefined && role === DIRECTOR_ROLE) {
290
+ prompt = prependPromptNote(prompt, rejectedReview);
256
291
  }
257
292
  // Likewise a change leftover recovery discarded as unmergeable: named until the role queues
258
- // its next change (the discarding tick itself appends it after recovery — see runTick).
293
+ // its next change (the discarding run itself appends it after recovery — see runOnce).
259
294
  const discard = state.conflictDiscard;
260
295
  if (discard)
261
296
  prompt = appendPromptNote(prompt, buildConflictDiscardNote(discard.summary, discard.attempts));
262
- // A fresh tick after the previous run(s) were cut off at the context ceiling (the loop
297
+ // A fresh run after the previous run(s) were cut off at the context ceiling (the role
263
298
  // stopped resuming, or never resumed — the director re-runs its prompt fresh): the only
264
299
  // memory that the last attempt was too big for the window is this note.
265
300
  if ((state.cutOffStreak ?? 0) > 0) {
@@ -1,26 +1,52 @@
1
- import { agentSessionResumable } from "../agent/agent-backend.js";
1
+ import { backendSessionResumable } from "../agent/agent-backend.js";
2
2
  import { latestSession } from "../agent/session-ledger.js";
3
3
  import { RETRIABLE_MERGE_RESULTS } from "../merge/merge-core.js";
4
4
  import { sessionDir } from "../paths.js";
5
5
  import { buildHandoffPrompt, buildResumePrompt } from "../prompt/prompt-followup.js";
6
- /** Decide how a tick starts, consuming the resume flags as a side effect (the flag is
7
- * consumed here so a resume that fails falls back to a normal fresh tick; another shutdown
8
- * mid-resume sets it again) and reclaiming the interrupted tick's re-queued prompt. Returns
9
- * null — meaning the tick is skipped — when the loop has nothing to run (an empty director
10
- * inbox); a role loop's assembly never returns null. */
11
- export function planTickStart(opts) {
6
+ import { appendPromptNote } from "../text/format.js";
7
+ /** Whether a run takes back the work an earlier run of its role left unsettled
8
+ * (src/git/slot-salvage.ts): an authoring run of the role's own work does, while a promptless
9
+ * recovery run (it authors nothing) and a user's request (the edits are not about it) leave the
10
+ * patch waiting for a later run. */
11
+ export function takesSalvagedWork(plan) {
12
+ return plan.prompt !== null && plan.userPrompt === null;
13
+ }
14
+ /** The plan of a run whose fresh checkout just had its role's salvaged work restored into it
15
+ * (`restored`, the patch's root-relative path). A fresh run becomes a `salvaged` handoff over
16
+ * those edits: a fresh session told they are there, which skips the fresh path's reset as every
17
+ * handoff does. A resume or handoff keeps its plan: its kept slot was lost, and the restore gives
18
+ * it back the edits it expects. `note`, when the restore left conflicts or hunks it could not
19
+ * place (buildSalvageRestoreNote), is appended to either prompt. */
20
+ export function planWithSalvagedWork(role, plan, restored, note) {
21
+ if (plan.prompt === null)
22
+ return { ...plan, restored };
23
+ const handingOff = !plan.resuming && !plan.handoff;
24
+ const prompt = handingOff ? buildHandoffPrompt(role, plan.prompt, "salvaged") : plan.prompt;
25
+ return {
26
+ ...plan,
27
+ ...(handingOff ? { handoff: true, resumeCause: "salvaged" } : {}),
28
+ prompt: note === undefined ? prompt : appendPromptNote(prompt, note),
29
+ restored,
30
+ };
31
+ }
32
+ /** Decide how a run starts, consuming the resume flags as a side effect (the flag is
33
+ * consumed here so a resume that fails falls back to a normal fresh run; another shutdown
34
+ * mid-resume sets it again) and reclaiming the interrupted run's re-queued prompt. Returns
35
+ * null — meaning the run is skipped — when the role has nothing to run (an empty director
36
+ * inbox); a role runner's assembly never returns null. */
37
+ export function planRunStart(opts) {
12
38
  const s = opts.state;
13
39
  // The last merge's failure, read before the clear below: a merge that failed
14
40
  // non-terminally kept its pin (the merger's own vocabulary — review_error, merge_conflict,
15
- // merge_blocked — with the detail it wrote into `lastError`), and when this tick's recovery
16
- // re-queues that pin, the failure must still feed the error streak (finishRecoveryTick).
41
+ // merge_blocked — with the detail it wrote into `lastError`), and when this run's recovery
42
+ // re-queues that pin, the failure must still feed the error streak (finishRecoveryRun).
17
43
  const priorMergeFailure = s.lastResult !== undefined && RETRIABLE_MERGE_RESULTS.has(s.lastResult)
18
44
  ? (s.lastError ?? `merge failed: ${s.lastResult}`)
19
45
  : undefined;
20
46
  s.lastError = undefined;
21
- // A tick interrupted by a harness shutdown left its pi session and its worktree's
47
+ // A run interrupted by a harness shutdown left its pi session and its worktree's
22
48
  // uncommitted edits in place: resume that session instead of starting fresh. The flag
23
- // is consumed here so a resume that fails falls back to a normal fresh tick; another
49
+ // is consumed here so a resume that fails falls back to a normal fresh run; another
24
50
  // shutdown mid-resume sets it again. Nothing to resume (sessions pruned, or pi never
25
51
  // started) also falls back to fresh.
26
52
  //
@@ -34,7 +60,7 @@ export function planTickStart(opts) {
34
60
  const resumePending = s.resumePending === true;
35
61
  // The previous run's own verdict that its `--resume` target is gone is authoritative over the
36
62
  // transcript check: a lingering transcript (a torn/empty file, or the fake's planted one) must
37
- // not re-arm a resume the backend just rejected, or the loop re-resumes and re-fails forever.
63
+ // not re-arm a resume the backend just rejected, or the role re-resumes and re-fails forever.
38
64
  const resumeMissing = s.resumeMissing === true;
39
65
  const latest = resumePending ? latestSession(dir) : null;
40
66
  // Our backend decides whether the newest session of this kind is actually resumable: pi
@@ -42,18 +68,18 @@ export function planTickStart(opts) {
42
68
  // claude resumes an id (safe across a newer gate line). A backend with no `resumable` hook
43
69
  // falls back to the backend-neutral session-file check.
44
70
  const resumable = resumePending && !resumeMissing
45
- ? agentSessionResumable(dir, { kind: "author", agent }, opts.claudeConfigDir)
71
+ ? backendSessionResumable(dir, { kind: "author", agent }, opts.claudeConfigDir)
46
72
  : false;
47
73
  const resumableSession = resumable;
48
74
  // A newest line our backend cannot resume is not ours to continue: another kind's or
49
75
  // another agent's session, OR our own author line whose session the backend no longer holds
50
76
  // (a `--resume` that answered "No conversation found" mid-run, leaving `resumeMissing`, or a
51
77
  // pruned session dir). In every such case the interrupted run's uncommitted edits still
52
- // deserve a handoff rather than the fresh path's worktree reset: the next tick runs a FRESH
53
- // session over the kept worktree, carrying the assembled tick prompt plus the handoff
78
+ // deserve a handoff rather than the fresh path's worktree reset: the next run runs a FRESH
79
+ // session over the kept worktree, carrying the assembled run prompt plus the handoff
54
80
  // bridge (plans/agent-backends.md "Sessions"). The failed `resumeMissing` run arms
55
81
  // `resumePending` and records `resumeMissing` from the verdict (its error turn carries no
56
- // assistant content), so the very next tick ends up here with the worktree intact. A dir with
82
+ // assistant content), so the very next run ends up here with the worktree intact. A dir with
57
83
  // no ledger line at all is not a handoff — there is no recorded interrupted session to carry
58
84
  // forward.
59
85
  // Captured before the flag is consumed below; a stale cause must not leak into a later resume.
@@ -63,7 +89,7 @@ export function planTickStart(opts) {
63
89
  // and claude keeps no flags across `--resume` (plans/agent-backends.md §7, part 7a/7).
64
90
  const budgetCapped = pendingResumeCause === "budget-capped";
65
91
  // An image-blind run with an image-capable peer tier ends `aborted` with this cause
66
- // (plans/image-input.md part 4/5): the next tick runs a FRESH session on the TARGET seam
92
+ // (plans/image-input.md part 4/5): the next run runs a FRESH session on the TARGET seam
67
93
  // over the kept worktree — never `--continue` — because the old session carries pi's
68
94
  // "image omitted" note and the target may be a different backend.
69
95
  const imageReroute = pendingResumeCause === "image-reroute";
@@ -73,7 +99,7 @@ export function planTickStart(opts) {
73
99
  s.resumeMissing = undefined;
74
100
  // An interruption during the review gate leaves the author's work fully committed — there
75
101
  // is nothing left to finish in its session. Recover (and re-review) the leftover commits
76
- // via a fresh tick instead: continuing the author session would burn a run on finished
102
+ // via a fresh run instead: continuing the author session would burn a run on finished
77
103
  // work, and any uncommitted edits are the reviewer's stray output, discarded by the fresh
78
104
  // path's reset below.
79
105
  const resumeBlocked = s.phase === "review";
@@ -81,14 +107,14 @@ export function planTickStart(opts) {
81
107
  // when its transcript is still resumable: it hands off instead.
82
108
  const resuming = resumableSession && !resumeBlocked && !budgetCapped && !imageReroute;
83
109
  const handingOff = handoff && !resumeBlocked;
84
- // Reclaim the prompt the interrupted tick re-queued for its resume (src/inbox/pending-prompt.ts):
85
- // the resumed session still owns that request in its context, so this tick's bookkeeping
110
+ // Reclaim the prompt the interrupted run re-queued for its resume (src/inbox/pending-prompt.ts):
111
+ // the resumed session still owns that request in its context, so this run's bookkeeping
86
112
  // operates on the queue's copy. A handoff runs a FRESH session, so it does not reclaim — the
87
- // fresh tick dequeues the re-queued request like any other. Consumed even when this tick does
113
+ // fresh run dequeues the re-queued request like any other. Consumed even when this run does
88
114
  // not resume, so a stale record never survives into a later resume.
89
115
  opts.pending.reclaimForResume(s, resuming);
90
116
  // Why the resume: a named quiet-kill means the last run died on a stalled tool call (the
91
- // bridge then warns against re-running it unchanged) or on the tick deadline while still
117
+ // bridge then warns against re-running it unchanged) or on the run deadline while still
92
118
  // making progress (the bridge asks for the smallest finish against the same limit); a
93
119
  // cut-off streak means the last run ran out of context and pi compacted the session (the
94
120
  // bridge asks for the smallest finish); otherwise a shutdown/crash. A handoff names itself,
@@ -105,7 +131,7 @@ export function planTickStart(opts) {
105
131
  prompt = buildResumePrompt(opts.role, resumeCause, agent);
106
132
  }
107
133
  else {
108
- const assembled = opts.tickPrompt(agent, opts.compact);
134
+ const assembled = opts.runPrompt(agent);
109
135
  // The image-reroute handoff note names the model the blind run was on and the image it
110
136
  // read; those facts ride the one-shot marker, which is consumed here. A handoff's fresh
111
137
  // session also needs the to-be-run model named for its own side of the note, but the
@@ -122,9 +148,12 @@ export function planTickStart(opts) {
122
148
  ? buildHandoffPrompt(opts.role, assembled, resumeCause, rerouteFacts)
123
149
  : assembled;
124
150
  }
125
- if (prompt === null)
126
- return null;
127
- // The raw user prompt a director tick is executing (null for role loops), so an
151
+ if (prompt === null) {
152
+ if (!opts.recoveryOnly)
153
+ return null;
154
+ return { priorMergeFailure, resuming: false, handoff: false, resumeCause, prompt: null, userPrompt: null };
155
+ }
156
+ // The raw user prompt a director run is executing (null for role runners), so an
128
157
  // unfulfilled outcome below can re-queue it. Captured before the field is cleared.
129
158
  const userPrompt = opts.pending.get();
130
159
  return { priorMergeFailure, resuming, handoff: handingOff, resumeCause, prompt, userPrompt };