tumwater 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +55 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +6 -4
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +404 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +65 -3
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +181 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +183 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,21 +1,37 @@
1
- // vocabulary: plain
2
1
  import { shortSha } from "../text/format.js";
3
2
  import { KILL_BY_PID_RULE, OLDER_NAMES_SENTENCE, TEST_RUNNER_RULE, dateLine } from "./prompt.js";
4
- import { NEEDS_REPLAN_NOTE, NEEDS_REVIEW_NOTE } from "../roles/role-guidance.js";
5
3
  import { describeCheck } from "../build/build-check-report.js";
6
- import { formatTimestamp } from "../text/datetime.js";
7
4
  import { numberedList } from "../text/markdown.js";
8
- import { SUMMARY_BLOCK, VERDICT_ENDING, NOTHING_TO_DO } from "../verdict/reply-contract.js";
5
+ import { VERDICT_ENDING } from "../verdict/reply-contract.js";
6
+ import { joinParts } from "../text/text.js";
7
+ import { FINISHED_ARCHIVES } from "../backlog/backlog-md.js";
9
8
  /** Prompts for the merge gate's pi runs — the runs the merge/review pipeline starts, not the
10
- * role loops' authoring ticks (those live in prompt.ts): conflict resolution after a rebase
11
- * (merge-to-main.ts), the adversarial pre-merge review (review.ts), and the notes that carry the
12
- * gate's verdicts back to the loop that authored the change (loop-tick.ts, tick-prompt.ts,
13
- * main-red.ts). The machine-detectable half of the gate's reply contract — verdict constants
14
- * and detection for parsing a reviewer's VERDICT line — lives in reply-contract.ts. */
9
+ * roles' authoring runs (those live in prompt.ts): conflict resolution after a rebase
10
+ * (merge-to-main.ts), the adversarial pre-merge review (review.ts), and the conflict hand-back
11
+ * note. The other notes that carry the gate's verdicts back to the role that authored the
12
+ * change live in gate-notes.ts. The machine-detectable half of the gate's reply contract —
13
+ * verdict constants and detection for parsing a reviewer's VERDICT line — lives in
14
+ * reply-contract.ts. */
15
15
  /** The listed-reasons placeholder the review and revision notes share when a rejection recorded
16
16
  * none: one home so a revision round's note and the next rejection's note cannot disagree on
17
17
  * how an empty objection list reads. */
18
- const NO_REASONS = "(no reasons recorded)";
18
+ export const NO_REASONS = "(no reasons recorded)";
19
+ /** Check 4's note on how a finished entry reads in the diff. The author pastes it under the live
20
+ * file's finished section, and the commit-time sweep (src/backlog/backlog-archive.ts) moves it
21
+ * to the top of the archive before the gate builds the diff. So a correct change shows the
22
+ * entry leaving PLANS.md or BUGS.md for PLANS-DONE.md or BUGS-FIXED.md. A reviewer that did
23
+ * not know this rejected such changes as misfiled. One change spent all four rounds asking for
24
+ * the entry under PLANS.md's `## Done`, a move the author cannot make stick (2026-10-21).
25
+ * Built from FINISHED_ARCHIVES so the names cannot drift from the sweep's. */
26
+ const ARCHIVE_SWEEP_SENTENCE = (() => {
27
+ const sections = FINISHED_ARCHIVES.map((a) => `${a.file}'s \`## ${a.section}\``).join(" or ");
28
+ const archives = FINISHED_ARCHIVES.map((a) => a.archive).join(" or ");
29
+ return `A finished entry reaches this diff already archived: the author pastes it under
30
+ ${sections}, and tumwater's commit-time sweep moves it
31
+ from there to the top of ${archives}, leaving that heading empty. An
32
+ entry that leaves the live file for its archive, closing stamp added, is the expected update,
33
+ not a filing error.`;
34
+ })();
19
35
  /** The two labeled blocks that show the resolver what each side meant, framed as data, not
20
36
  * instructions, so pi cannot mistake a commit body for a rule. */
21
37
  function conflictIntentBlocks(intent) {
@@ -54,8 +70,8 @@ function conflictedFileLine(file) {
54
70
  function listsHunks(files) {
55
71
  return files.some((f) => typeof f !== "string" && f.hunks.length > 0);
56
72
  }
57
- /** The prompt for resolving merge conflicts left in a loop's worktree. `check` — the project's
58
- * resolved check, as the tick prompt threads it — is named so the resolver knows which command
73
+ /** The prompt for resolving merge conflicts left in a role's worktree. `check` — the project's
74
+ * resolved check, as the run prompt threads it — is named so the resolver knows which command
59
75
  * the harness will run (told only to keep the tests passing, the 2026-10-04 resolver guessed
60
76
  * `npx vitest run` in a node:test repo, BUGS.md 2026-10-05), and the rules tell it not to run
61
77
  * that check itself: merge-to-main.ts's verifyMerge runs it on the resolved tree before
@@ -125,24 +141,26 @@ ${TEST_RUNNER_RULE}${noCheckRunRule}
125
141
  /** The prompt for the adversarial pre-merge review gate: a fresh-session pi run that sees only the
126
142
  * diff and project context — never the author's session — and replies with exactly one VERDICT
127
143
  * line plus numbered reasons (see parseVerdict in src/review/review-verdict.ts).
128
- * `verifiedByHarness` names the project's own check the gate's deterministic pre-check already ran
129
- * green on this exact tree (e.g. "`npm run test` passed"), so the reviewer spends its run on what a
130
- * green suite cannot show instead of re-running it. The no-re-run instruction is its own line in
131
- * the rules list, not a clause in the context paragraph: stated there, reviewers read past it and
132
- * re-ran the suite in scratch copies under /tmp while holding the merge slot (BUGS.md
133
- * 2026-09-23). It appears only when a verified result exists: after a timed-out, killed, or skipped
134
- * pre-check no green run stands behind the tree, and a reviewer running the suite itself is doing
135
- * its job. `baseRev` is the frozen revision the diff is measured against (changeBaseRev's
144
+ * The prompt always says tumwater is running the project's own check on this tree (alongside
145
+ * the review, or already done), so the reviewer spends its run on what a green suite cannot show
146
+ * instead of running it. The no-re-run instruction is its own line in the rules list, not a clause
147
+ * in the context paragraph: stated there, reviewers read past it and re-ran the suite in scratch
148
+ * copies under /tmp while holding the merge slot (BUGS.md 2026-09-23). `baseRev` is the frozen
149
+ * revision the diff is measured against (changeBaseRev's
136
150
  * merge-base, resolved when the gate builds the diff): the prompt names it and tells the reviewer
137
- * to compare against it, never `main`, because other loops merge to main while the review runs
151
+ * to compare against it, never `main`, because other roles merge to main while the review runs
138
152
  * (BUGS.md 2026-10-06). Omitted, the prompt keeps the generic "ahead of main" wording. `today` pins
139
153
  * the date line (prompt.ts's dateLine) for tests; omitted, it is the local day. The text must
140
154
  * contain the literal "VERDICT:" exactly twice — the two advertised forms — because a prompt
141
155
  * test derives the accepted forms from it. `priorReview` is present only when this merge is a
142
156
  * revision of a previously rejected change (plans/revise-rejected.md part 2/2): it names the round
143
157
  * and shows the prior objections plus the interdiff so the re-review checks each one first.
144
- * Omitted, the prompt is unchanged from a fresh change's. */
145
- export function buildReviewPrompt(diff, summary, commitBody, principles, highFriction, verifiedByHarness, baseRev, today, priorReview) {
158
+ * Omitted, the prompt is unchanged from a fresh change's. `postMerge` marks the post-merge review
159
+ * of a commit already on main (review.afterMerge): a finding the reviewer can repair is the work —
160
+ * it makes the minimal fix in the worktree, leaves the edit, and ends with the verdict line plus a
161
+ * `FIX:` line — so the instruction and the repair rule render only for that caller, and every other
162
+ * (pre-merge) caller's prompt is byte-identical. */
163
+ export function buildReviewPrompt(diff, summary, commitBody, principles, highFriction, baseRev, today, priorReview, knownMainFailures, postMerge) {
146
164
  const parts = [
147
165
  `You are an adversarial code reviewer for tumwater, which runs a team of AI roles on this project. ${OLDER_NAMES_SENTENCE} A
148
166
  role's change is about to be merged to main; you decide whether it may merge. You have no context
@@ -172,13 +190,23 @@ from a new finding. The prior objections were:\n${list}\nThe interdiff
172
190
  between the rejected version and this revision (what the revision changed, main's own movement
173
191
  excluded):\n<interdiff>\n${priorReview.interdiff}\n</interdiff>`);
174
192
  }
175
- if (verifiedByHarness)
176
- parts.push(`Tumwater already ran the project's own check on this exact tree and it passed:
177
- ${verifiedByHarness}. Spend your run on what a green check cannot show: wrong behavior the tests
178
- never exercise, claims the diff does not back, work left half-done.`);
193
+ parts.push(`Tumwater is running the project's own check on this tree; do not run the suite. Spend your run
194
+ on what a green check cannot show: wrong behavior the tests never exercise, claims the diff does
195
+ not back, work left half-done.`);
196
+ if (knownMainFailures && knownMainFailures.length > 0)
197
+ parts.push(`Tumwater ran the project's own check on this tree and it failed, but only on tests main already
198
+ fails, or that failed at this change's base and pass on main's tip now — not this change's failures:\n${knownMainFailures.map((t) => `- ${t}`).join("\n")}
199
+ Do not count them against the change unless the diff touches what they test.`);
179
200
  if (principles) {
180
201
  parts.push(`Design principles this project holds — your review standard; a violation of one is a finding:\n<principles>\n${principles}\n</principles>`);
181
202
  }
203
+ if (postMerge) {
204
+ parts.push(`This review judges a change already on main, so a finding you can repair IS the work: make the
205
+ minimal fix right here in the worktree, leave the edit in place, and end with your verdict line plus
206
+ a \`FIX:\` line naming the files you changed — tumwater commits and merges your repair as one fix.
207
+ Fix only what the diff shows is wrong, nothing more. A finding you cannot repair in this worktree is
208
+ recorded as numbered reasons, exactly as a pre-merge rejection is.`);
209
+ }
182
210
  // The base the diff is measured from, named in the prompt: main moves while a merge is approved,
183
211
  // and a reviewer that double-checks scope with `git diff main` reads a newer merge, reversed, as
184
212
  // part of the change (BUGS.md 2026-10-06). Sentence only when a base is given; the callers that
@@ -191,14 +219,25 @@ never exercise, claims the diff does not back, work left half-done.`);
191
219
  parts.push(`The full diff this merge will land (${diffBase}):\n<diff>\n${diff}\n</diff>`);
192
220
  // Right after the scratch-copy allowance it qualifies: a scratch copy stays fine for measuring
193
221
  // something, but a copy made to run the suite is the re-run this line forbids.
194
- const noRerunRule = verifiedByHarness
195
- ? `
196
- - Do not re-run the check named above or the project's full test suite: tumwater's green run
197
- is the verified result. Copying the tree elsewhere to run it — rsync or cp into a temp
222
+ const noRerunRule = `
223
+ - Do not run the project's check or its full test suite: tumwater is running it on this tree and
224
+ owns the result. Copying the tree elsewhere to run it — rsync or cp into a temp
198
225
  directory, reinstalling dependencies there (for an npm project, \`npm ci\`; otherwise its
199
- equivalent) — counts as re-running it. Running one
200
- specific test file for a concrete reason you can name is fine.`
201
- : "";
226
+ equivalent) — counts as running it. Running one
227
+ specific test file for a concrete reason you can name is fine.`;
228
+ // The worktree rule differs by mode: a pre-merge reviewer must not touch the tree it is judging,
229
+ // while a post-merge reviewer's whole point is to repair what it finds in this worktree (the
230
+ // harness commits the edit). The pre-merge text is byte-identical to the line it replaces.
231
+ const worktreeRule = postMerge
232
+ ? `- Make your repair directly in this worktree and leave it: the files you edit are what
233
+ tumwater commits as one fix commit. Never run a git command that changes state (no git add,
234
+ commit, merge, rebase, reset) — tumwater owns every git operation and commits for you. Reading
235
+ files and git history is fine; a scratch copy under the system temp directory is fine when you
236
+ need to run something.`
237
+ : `- Do not edit any file in the worktree. Never run a command that changes state (no git
238
+ add/commit/merge/rebase/reset, no writes in the worktree) — your only output channel is the
239
+ verdict below. Reading files and git history is fine; a scratch copy under the system temp
240
+ directory is fine when you need to run something.`;
202
241
  parts.push(`Review adversarially: hunt for correctness bugs, violations of the project's principles,
203
242
  unjustified complexity growth, and incomplete or half-done work. Work in this order:
204
243
  1. The diff above is current — read it there; do not fetch it again with \`git diff\`,
@@ -219,8 +258,8 @@ Check, in this order:
219
258
  1. Does the diff do exactly what the summary and WHY claim — no more, no less? An unclaimed
220
259
  change is a finding.
221
260
  2. Are the VERIFIED claims consistent with the diff (commands, files, observations)? A claim you
222
- can disprove is a rejection. Suite counts are tumwater's own attestation — they appear in
223
- the passed-check line above when the check ran green — so a count missing from VERIFIED is
261
+ can disprove is a rejection. Suite counts are tumwater's own attestation — it runs the
262
+ check and records them — so a count missing from VERIFIED is
224
263
  not a finding, and neither is an author-stated count that differs from it: judge whether the
225
264
  tests exist and exercise the change (check 3), not the author's arithmetic.
226
265
  3. Do new or changed tests exercise the new behavior — would they fail without the change?
@@ -231,24 +270,24 @@ Check, in this order:
231
270
  direction is not itself a defect. Reject only if the change is incoherent or incomplete for
232
271
  its stated purpose, or leaves the existing entry stale and contradictory (updating the entry
233
272
  in place is the author's duty).
273
+ ${ARCHIVE_SWEEP_SENTENCE}
234
274
  5. Does anything violate a principle above, or grow complexity the WHY does not justify?
235
275
  Reject only for concrete, verifiable defects you can name; when the change is correct, complete
236
276
  for its stated purpose, and within the principles, taste alone is not a rejection.
237
277
 
238
278
  Rules for this run:
239
- - Do not edit any file in the worktree. Never run a command that changes state (no git
240
- add/commit/merge/rebase/reset, no writes in the worktree) — your only output channel is the
241
- verdict below. Reading files and git history is fine; a scratch copy under the system temp
242
- directory is fine when you need to run something.${noRerunRule}
279
+ ${worktreeRule}${noRerunRule}
243
280
  ${TEST_RUNNER_RULE}
244
281
  - ${KILL_BY_PID_RULE}
245
282
  - Weigh the change against its stated purpose; do not approve work you did not actually check.
246
283
  - End your reply with exactly one line in this form:
247
- ${VERDICT_ENDING.replace(/\n/g, "\n ")}`);
284
+ ${VERDICT_ENDING.replace(/\n/g, "\n ")}${postMerge ? `
285
+ plus, when you repaired a finding, one \`FIX:\` line naming the comma-separated files you edited:
286
+ FIX: <files you changed>` : ""}`);
248
287
  return parts.join("\n\n");
249
288
  }
250
289
  /** The prompt for the next attempt at a review whose previous run hit its time budget while
251
- * still making progress (LoopState.reviewResume): that run continues the timed-out reviewer's
290
+ * still making progress (RunnerState.reviewResume): that run continues the timed-out reviewer's
252
291
  * own session (`--continue`), so the diff and everything the reviewer already read are in
253
292
  * context. This prompt carries no diff — re-sending it would defeat the resume — and names the
254
293
  * frozen comparison base and the unchanged patch-id so the reviewer knows the change it was
@@ -290,17 +329,32 @@ this worktree, not in a scratch copy. Finish the review now from what you have a
290
329
  reply with ONLY the closing block — no tool calls, no other text — one line in this form:
291
330
  ${VERDICT_ENDING}`;
292
331
  }
293
- /** The one-turn follow-up sent into a tick's OWN session (--continue) when the harness's
332
+ /** The one-turn follow-up sent into a run's OWN session (--continue) when the harness's
294
333
  * pre-queue self-check found problems likely to get the change rejected. Only some findings are
295
334
  * the merge gate's own deterministic checks; the rest (path drift, plan dependencies) are
296
335
  * heuristics, so the prompt never claims the gate rejects every one — claiming it did made an
297
336
  * author sweep a rename through the backlog's history (2026-10-18). The session still holds
298
337
  * everything the run did, so the author fixes the faults before the change commits and queues —
299
338
  * the gate keeps its final say, and the fix costs one bounded turn instead of a rejection cycle.
300
- * A finding the author believes is wrong is answered in RISK rather than forced away; the reply
301
- * re-states the closing block because the harness re-derives the commit message from it.
302
- * Carries the tick prompt's no-git rule (the harness commits). */
303
- export function buildStageFixPrompt(findings) {
339
+ * A finding the author believes is wrong is answered in RISK rather than forced away.
340
+ * The prompt shows the change's description as the harness holds it and asks only for this
341
+ * turn's RISK and VERIFIED, which the harness appends to that description. Asked for "the
342
+ * closing block again", authors described the turn itself ("No change — the finding is wrong"),
343
+ * and that became the subject of a whole feature (BUGS.md 2026-10-21). A change whose subject
344
+ * was derived from its paths (`change.summary` null) is also asked for a SUMMARY of the whole
345
+ * change. Carries the run prompt's no-git rule (the harness commits). */
346
+ export function buildStageFixPrompt(findings, change) {
347
+ const current = joinParts([change.summary !== null && `SUMMARY: ${change.summary}`, change.body], "\n");
348
+ const description = current === ""
349
+ ? ""
350
+ : `Your change's description stays as your closing block gave it, and review checks it against
351
+ the whole diff:
352
+ ${current.replace(/^/gm, " ")}
353
+ Do not rewrite it to describe this turn.
354
+ `;
355
+ const summaryLine = change.summary === null
356
+ ? " SUMMARY: <imperative one-line description of the whole change, not of this turn, at most 72 characters>\n"
357
+ : "";
304
358
  return `Your change is about to commit, but tumwater's pre-queue self-check found problems the
305
359
  merge gate or its review is likely to reject:
306
360
  ${numberedList(findings)}
@@ -308,137 +362,59 @@ Fix each one in the worktree now with the smallest edit that resolves it, and ke
308
362
  as it is. When a finding is wrong, say why in your RISK line instead of forcing a change that
309
363
  does not help. Do not run any git command that changes state (no add, commit, reset, checkout) —
310
364
  tumwater commits for you.
311
- When done, reply with ONLY the closing block again — no tool calls, no other text, one line each:
312
- ${SUMMARY_BLOCK}`;
365
+ ${description}When done, reply with ONLY these lines about this turn — no tool calls, no other text, one line
366
+ each. tumwater adds them to the change's RISK and VERIFIED lines, marked as the self-check's:
367
+ ${summaryLine} RISK: <for each finding, what you fixed, or why it is wrong and you left it>
368
+ VERIFIED: <what you ran or read to check this turn's fixes — write none when nothing was run>`;
313
369
  }
314
- /** The note injected into a role's next tick prompt after its previous change was rejected in
315
- * review. Every tick starts a fresh pi session, so this is the only cross-tick memory of what
316
- * was built and why it failed — it carries the full reasons, not a summary of them. The whole
317
- * `lastReview` rides in, not just the reasons: the timestamp and reviewed head let the author
318
- * weigh a stale objection against main's current state (main may have satisfied it since). */
319
- export function buildRejectedReviewNote(review, role) {
320
- const list = numberedList(review.reasons, NO_REASONS);
321
- let context = "";
322
- if (review.at !== undefined) {
323
- context = ` (${formatTimestamp(review.at)}`;
324
- if (review.head)
325
- context += `, head ${shortSha(review.head)}`;
326
- context += ")";
370
+ /** How much of the hunks a salvage restore could not place one note quotes, per file and in all:
371
+ * enough to apply a typical leftover by hand without letting one huge rejected file crowd out
372
+ * the run's own prompt. The full patch is always named for the rest. */
373
+ const UNPLACED_FILE_CHARS = 4_000;
374
+ const UNPLACED_TOTAL_CHARS = 16_000;
375
+ /** The note a run gets when the salvaged work restored into its worktree (src/git/slot-salvage.ts)
376
+ * did not all go back cleanly because its branch moved since the work was saved: the files left
377
+ * with conflict markers, with their hunks' line ranges, and the hunks git could not place at all,
378
+ * quoted for the run to apply by hand (a binary file only named), with the saved patch's path for
379
+ * anything cut. Undefined when everything went back cleanly. */
380
+ export function buildSalvageRestoreNote(conflicted, unplaced, patchPath) {
381
+ if (conflicted.length === 0 && unplaced.length === 0)
382
+ return undefined;
383
+ const parts = ["The saved edits did not all go back cleanly: your branch moved since they were saved."];
384
+ if (conflicted.length > 0) {
385
+ const readHint = listsHunks(conflicted)
386
+ ? "\nRead each listed hunk's line range plus a few lines of context, not a conflicted file from the top."
387
+ : "";
388
+ parts.push(`These files carry conflict markers: \`<<<<<<< ours\` holds the branch's current text and
389
+ \`>>>>>>> theirs\` the saved edits. Resolve every marker (<<<<<<<, =======, >>>>>>>), keeping the
390
+ intent of both sides:
391
+ ${conflicted.map(conflictedFileLine).join("\n")}${readHint}`);
327
392
  }
328
- // On exhaustion the rejected diff is gone. Feature's rejected change often implemented a
329
- // PLANS.md entry, and re-authoring the same plan from scratch is what produced the long
330
- // rejection runs this note exists to end, so feature is sent to replan the entry instead.
331
- // A plan-less role has no downstream loop to replan for it, so it is sent to its own
332
- // notebook instead: it records the abandoned purpose and any instances that remain
333
- // unhandled, then takes a different theme on the next tick. Re-authoring the same purpose
334
- // from the same objections is what restarted the round and produced the long rejection
335
- // runs this note exists to end. The markdown-only replan note merges review-exempt, but a
336
- // merged change clears the standing rejection (clearSupersededRejection, tick-apply.ts), so
337
- // the instruction does not re-inject once the note has merged.
338
- let finality = "";
339
- if (review.exhausted) {
340
- finality =
341
- role === "feature"
342
- ? ` This was the change's final revision round — the same objection survived two revisions (or the chain hit its cap), so the rejected diff and ref are gone. If it implemented a PLANS.md entry, do not re-author it: append ${NEEDS_REPLAN_NOTE} under that entry's heading, with the numbered objections above quoted beneath it, and merge that markdown-only change and nothing else so the plan role can rewrite the plan. Otherwise, redo the change against current main.`
343
- : " This was the change's final revision round — the same objection survived two revisions (or the chain hit its cap), so the rejected diff and ref are gone. Do not re-author it. Record the abandoned purpose and any instances of it that remain unhandled in your notes, then take a different theme on this run: re-deriving the same purpose from the same objections is what produced the long rejection runs this note exists to end.";
393
+ if (unplaced.length > 0) {
394
+ let budget = UNPLACED_TOTAL_CHARS;
395
+ let cut = 0;
396
+ const quoted = [];
397
+ for (const { path, diff } of unplaced) {
398
+ const body = diff.includes("GIT binary patch")
399
+ ? "(a binary file: its saved version is in the patch below)"
400
+ : diff.length > UNPLACED_FILE_CHARS
401
+ ? `${diff.slice(0, UNPLACED_FILE_CHARS)}\n… (cut; the rest is in the patch below)`
402
+ : diff.trimEnd();
403
+ if (body.length > budget) {
404
+ cut += 1;
405
+ continue;
406
+ }
407
+ budget -= body.length;
408
+ quoted.push(`<diff path="${path}">\n${body}\n</diff>`);
409
+ }
410
+ parts.push(`These saved hunks could not be placed at all. Apply what still makes sense by hand, and leave
411
+ out what the branch already does:
412
+ ${quoted.join("\n")}${cut > 0 ? `\n(${cut} more file(s) not shown: they are in the patch below.)` : ""}`);
344
413
  }
345
- return `Your previous change was rejected in review${context}:\n${list}\nAddress the objections or take a different approach.${finality}`;
346
- }
347
- /** The note injected into a role's tick prompt when its rejected change has been re-applied to
348
- * current main as uncommitted edits (plans/revise-rejected.md, loop/revision.ts's
349
- * applyRevision): this tick's one task is to revise the change already in the worktree, not
350
- * author a new one. The prior objections are numbered so the author can work through them; a
351
- * revision round past the first also reaches here. The chain keeps revising while each round
352
- * fixes the prior objection; it ends only on a repeated objection or the backstop (BUGS.md
353
- * 2026-10-14). A pure function so its shape is pinned in tests. */
354
- export function buildRevisionNote(review, round) {
355
- const list = numberedList(review.reasons, NO_REASONS);
356
- return `Your previously rejected change is already in the worktree as uncommitted edits — this
357
- is revision ${round}. This run's ONE task is to revise it; do not author anything else.
358
- The review objections were:\n${list}
359
- Fix each numbered objection with the smallest edit that resolves it, and keep everything else as it
360
- is. When an objection is about a build-check failure in a test the change does not touch that you
361
- cannot reproduce, say so in RISK and keep the change as it is. Rewrite the closing block so it
362
- describes the WHOLE change as it now stands:
363
- ${SUMMARY_BLOCK}
364
- When an objection shows the change should not exist (its premise is disproven, it duplicates main,
365
- or it has no reachable benefit), end your reply with the line ${NOTHING_TO_DO} instead, and
366
- tumwater drops the change.`;
414
+ parts.push(`The whole saved patch is at ${patchPath}; read it only if you need more than the above.`);
415
+ return `<salvage-restore>\n${parts.join("\n\n")}\n</salvage-restore>`;
367
416
  }
368
- /** The note appended to a work instance's tick prompt when it holds a claim (plans/parallel-
369
- * work-instances.md "Claims", part 4/7): the harness has already picked its entry, so the
370
- * charter's choosing step is replaced. A pure function so its shape is pinned in tests; the
371
- * range is optional because a claim whose entry was already released still gets the note. */
372
- export function buildAssignmentNote(claim, range, missingPaths = []) {
373
- const where = range ? ` (${claim.file} lines ${range.start}-${range.end})` : ` (${claim.file})`;
374
- const intro = claim.source === "staged"
375
- ? `Your change moved ONE backlog entry, so it is now your task, replacing your charter's "pick one" step:`
376
- : `Tumwater has assigned you ONE backlog entry, replacing your charter's "pick one" step:`;
377
- const shown = missingPaths.slice(0, 10).map((p) => `\`${p}\``).join(", ");
378
- const rest = missingPaths.length - 10;
379
- const missingLine = missingPaths.length === 0
380
- ? ""
381
- : `\nThe entry names paths that do not exist in your checkout: ${shown}${rest > 0 ? `, and ${rest} more` : ""}. Each was renamed or removed after the entry was written (find its successor with \`git log --follow --name-status -- <path>\` or by grepping for the symbols the entry names), or is a file the entry asks you to create.`;
382
- return `<assigned-entry>
383
- ${intro}
384
- "${claim.title}"${where}.
385
- Implement or fix only that entry. If it is too large for one run, append ${NEEDS_REVIEW_NOTE} under its heading and end your reply with that note alone. If it should not be done, append a Refused note under its heading with your objection and change nothing else. Do not edit any other backlog entry.${missingLine}
386
- </assigned-entry>`;
387
- }
388
- /** The note appended to an unassigned work instance's prompt when siblings hold backlog entries
389
- * (plans/parallel-work-instances.md "an exclusion list of held titles"): those entries are
390
- * someone else's task, so choosing among the rest — or ending with nothing to do — is the
391
- * instance's whole job. A pure function so its shape is pinned in tests. */
392
- export function buildHeldEntriesNote(titles) {
393
- return `<held-entries>
394
- Sibling instances hold these backlog entries; do not pick, edit, or implement them, and do not edit their headings. If every remaining entry is held, treat the backlog as having nothing to do for you:
395
- ${titles.map((t) => `- "${t}"`).join("\n")}
396
- </held-entries>`;
397
- }
398
- /** The capped series-part range list shared by the merged-prerequisites and waiting-parts
399
- * notes: at most five `- "title"` lines, each naming the entry's heading line range, then one
400
- * `- …and N more` line for the rest, so the two notes list a part's neighbors identically. A
401
- * pure function so its shape is pinned in tests (test/gate-prompts.test.ts). */
402
- /** The longest title a compact prompt quotes in an entry list: a PLANS.md heading's tail names
403
- * the plan's dates and every file it touches, which its range already leads to. */
404
- const COMPACT_TITLE_CHARS = 100;
405
- function entryRangeLines(entries, compact = false) {
406
- // Cut by code point, so an astral character is never split in half.
407
- const title = (t) => {
408
- const cps = Array.from(t);
409
- return compact && cps.length > COMPACT_TITLE_CHARS ? `${cps.slice(0, COMPACT_TITLE_CHARS).join("").trimEnd()}…` : t;
410
- };
411
- const lines = entries.slice(0, 5).map((e) => `- "${title(e.title)}" (${e.file ?? "PLANS.md"} lines ${e.start}-${e.end})`);
412
- if (entries.length > 5)
413
- lines.push(`- …and ${entries.length - 5} more`);
414
- return lines;
415
- }
416
- /** The note appended to a series part's assignment when parts it requires already merged: each
417
- * one's Done heading, file (PLANS-DONE.md once archived) and line range, so the implementer reads
418
- * exactly what that part changed instead of searching a history that runs to thousands of lines.
419
- * Lists at most five, then a count of the rest. A pure function so its shape is pinned in tests. */
420
- export function buildMergedPrerequisitesNote(entries,
421
- /** The compact prompt every run gets: each title cut to COMPACT_TITLE_CHARS. */
422
- compact = false) {
423
- const lines = entryRangeLines(entries, compact);
424
- return `<merged-prerequisites>
425
- Your assigned entry builds on parts that are already merged. Each one's Done entry records what it actually changed; read one by its range when your entry depends on its details, rather than searching the backlog files:
426
- ${lines.join("\n")}
427
- </merged-prerequisites>`;
428
- }
429
- /** The note injected into a role's prompt after leftover recovery discarded its pinned change
430
- * at MERGE_CONFLICT_LIMIT (src/loop/leftover.ts): at the cap recovery ordinarily hands the
431
- * change back to its author with markers instead, so a discard means that lineage already used
432
- * its hand-back. The merge failures were harness-level, so without the note the author would
433
- * never learn its work is gone — or that redoing it from memory of the old diff would conflict
434
- * again. A pure function so its shape is pinned in tests. */
435
- export function buildConflictDiscardNote(summary, attempts) {
436
- return (`Your previous change ("${summary}") was discarded without merging: it conflicted with main ` +
437
- `${attempts} times and conflict resolution could not reconcile it. Main has moved on since you ` +
438
- `wrote it. If the change is still needed, redo it against current main, starting from main's ` +
439
- `current version of the files it touched rather than your earlier diff.`);
440
- }
441
- /** The note appended to a role's tick prompt when a change was handed back to it with the
417
+ /** The note appended to a role's run prompt when a change was handed back to it with the
442
418
  * conflict markers left in place (PLANS.md "Robust conflict landing, part 2/2"): the merge
443
419
  * resolver could not settle it, so the author — who knows what the change was for — resolves it
444
420
  * over current main. `reason` says whether it was an approved change whose merge kept
@@ -467,46 +443,3 @@ ${files}${readHint}
467
443
  ${conflictIntentBlocks(intent)}
468
444
  </conflict-handback>`;
469
445
  }
470
- /** The note injected into the `bugfix` healer's prompt when main's own suite is red (PLANS.md
471
- * "Red-main handoff"): the gate's warning event is harness-level and no role prompt reads it, so
472
- * the one role allowed to author on a red main is told what failed and that fixing it is this
473
- * tick's job. The healer's normal charter is a starting point, not a fit — a red main blocks
474
- * every other code role, and the gate's deterministic pre-check rejects any change that leaves
475
- * the suite red, so authoring anything else first is waste. A pure function so its shape is
476
- * pinned in tests; script/headline are optional because a red carries a failing script, but the
477
- * note must still read sensibly if it is ever absent. */
478
- export function buildMainRedNote(sha, script, headline, tail) {
479
- const what = script
480
- ? `${script}${headline ? `: ${headline}` : ""}`
481
- : (headline ?? "the project's declared check");
482
- // A red baseline already carries the check's bounded clipped tail (clipBuildTail, ≤12 lines:
483
- // the failing test's name, the error-message line, then the stack). Hoist it into the note so
484
- // the healer starts from the actual assertion and stack instead of the one-line headline —
485
- // often just the test name — and must burn its run reproducing the failure first. The tail is
486
- // fenced and labeled, and never touches the directive lines above, so the failure's own text
487
- // cannot read as instructions to the role. An absent, empty, or all-blank tail renders today's
488
- // exact note. */
489
- const output = (tail ?? []).map((l) => l.trim()).filter((l) => l !== "");
490
- const tailBlock = output.length > 0
491
- ? `\nFailing check output (${output.length} lines):\n\`\`\`\n${output.join("\n")}\n\`\`\`\n`
492
- : "";
493
- return `<main-red>
494
- main's own suite is red at ${shortSha(sha)} (${what}). Every code-producing role is skipping authoring and no change merges until main is green.
495
- This run your job is to make main green: reproduce that failure, fix it (add a regression test where one is feasible), and merge the fix.
496
- The review gate's deterministic pre-check runs main + your change, so a change that leaves the suite red is rejected — do not author anything else first.
497
- If the failure is environmental — flaky, load-sensitive, or a broken local toolchain — say so plainly in your reply rather than editing unrelated code.
498
- ${tailBlock}</main-red>`;
499
- }
500
- /** The note appended to an assigned series part's prompt naming the later planned parts that
501
- * wait on it, so the implementer sees where its part ends and leaves their share alone. At most
502
- * five are listed, then a literal `- …and N more` line (the `moreSuffix` phrase renders inline,
503
- * not as a list line). A pure function so its shape is pinned in tests. */
504
- export function buildWaitingPartsNote(entries,
505
- /** The compact prompt every run gets: each title cut to COMPACT_TITLE_CHARS. */
506
- compact = false) {
507
- const shown = entryRangeLines(entries, compact);
508
- return `<waiting-parts>
509
- Later parts of your series wait for yours to land, and each one plans its own share of the work: do none of it here, and keep in place the names and files they say your part leaves behind. Read one by its range when you need to know what it expects:
510
- ${shown.join("\n")}
511
- </waiting-parts>`;
512
- }