tumwater 0.1.2 → 0.2.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 (361) hide show
  1. package/README.md +74 -108
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +24 -0
  4. package/dist/src/agent/agent-backend.js +2 -2
  5. package/dist/src/agent/backend-traits.js +60 -0
  6. package/dist/src/agent/claude/claude-account.js +61 -8
  7. package/dist/src/agent/claude/claude-args.js +12 -15
  8. package/dist/src/agent/claude/claude-backend.js +51 -41
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +7 -16
  11. package/dist/src/agent/image-input.js +107 -0
  12. package/dist/src/agent/notes-shim.js +41 -0
  13. package/dist/src/agent/opencode/opencode-args.js +61 -0
  14. package/dist/src/agent/opencode/opencode-backend.js +255 -0
  15. package/dist/src/agent/opencode/opencode-catalog.js +184 -0
  16. package/dist/src/agent/opencode/opencode-config.js +188 -0
  17. package/dist/src/agent/opencode/opencode-env.js +36 -0
  18. package/dist/src/agent/opencode/opencode-session-id.js +37 -0
  19. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  20. package/dist/src/agent/role-notes-write.js +16 -32
  21. package/dist/src/agent/run-agent.js +95 -18
  22. package/dist/src/agent/session-ledger.js +57 -1
  23. package/dist/src/backlog/backlog-archive.js +296 -0
  24. package/dist/src/backlog/backlog-cli.js +573 -0
  25. package/dist/src/backlog/backlog-commit.js +31 -0
  26. package/dist/src/backlog/backlog-eligibility.js +357 -33
  27. package/dist/src/backlog/backlog-md.js +98 -18
  28. package/dist/src/backlog/backlog-render.js +62 -11
  29. package/dist/src/backlog/backlog-structure.js +261 -58
  30. package/dist/src/backlog/backlog-write.js +197 -319
  31. package/dist/src/backlog/backlog.js +113 -16
  32. package/dist/src/backlog/question-answer.js +339 -0
  33. package/dist/src/baseline/main-baseline.js +75 -88
  34. package/dist/src/baseline/main-red.js +38 -30
  35. package/dist/src/brief/brief-edit.js +43 -0
  36. package/dist/src/{brief.js → brief/brief.js} +66 -10
  37. package/dist/src/budget/budget.js +32 -15
  38. package/dist/src/budget/fallback-breaker.js +8 -7
  39. package/dist/src/build/build-check-detect.js +8 -7
  40. package/dist/src/build/build-check-events.js +68 -22
  41. package/dist/src/build/build-check-report.js +22 -23
  42. package/dist/src/build/build-check-scoped.js +51 -62
  43. package/dist/src/build/build-check.js +15 -14
  44. package/dist/src/build/build-info.js +2 -1
  45. package/dist/src/build/build-stage.js +23 -22
  46. package/dist/src/build/dep-install.js +22 -21
  47. package/dist/src/build/host-sleep.js +1 -1
  48. package/dist/src/change/change-data.js +25 -14
  49. package/dist/src/change/change-render.js +11 -10
  50. package/dist/src/cli/brief-command.js +58 -0
  51. package/dist/src/cli/cli-args.js +25 -5
  52. package/dist/src/cli/cli-command-args.js +46 -14
  53. package/dist/src/cli/cli-editor.js +64 -0
  54. package/dist/src/cli/cli-flag-specs.js +54 -16
  55. package/dist/src/cli/cli-marker-commands.js +13 -10
  56. package/dist/src/cli/cli-output.js +9 -0
  57. package/dist/src/cli/cli-query-commands.js +458 -49
  58. package/dist/src/cli/cli-repo-gate.js +1 -1
  59. package/dist/src/cli/cli-run.js +208 -59
  60. package/dist/src/cli/completion.js +207 -0
  61. package/dist/src/cli/config-commands.js +14 -11
  62. package/dist/src/cli/help.js +305 -110
  63. package/dist/src/cli/log-commands.js +127 -21
  64. package/dist/src/cli/notify-command.js +33 -18
  65. package/dist/src/cli/question-commands.js +29 -224
  66. package/dist/src/cli.js +121 -40
  67. package/dist/src/collections.js +66 -16
  68. package/dist/src/concurrency/check-permit.js +16 -17
  69. package/dist/src/concurrency/lock.js +6 -5
  70. package/dist/src/concurrency/provider-lease.js +69 -0
  71. package/dist/src/concurrency/semaphore.js +258 -36
  72. package/dist/src/config/config-aliases.js +106 -0
  73. package/dist/src/config/config-editable-keys.js +3 -3
  74. package/dist/src/config/config-fallbacks.js +379 -0
  75. package/dist/src/config/config-field-checks.js +19 -10
  76. package/dist/src/config/config-keys.js +160 -0
  77. package/dist/src/config/config-live.js +64 -12
  78. package/dist/src/config/config-schema.js +34 -22
  79. package/dist/src/config/config-validation.js +132 -48
  80. package/dist/src/config/config-views.js +119 -337
  81. package/dist/src/config/config-write.js +138 -177
  82. package/dist/src/config/config.js +60 -49
  83. package/dist/src/config/model-selector.js +28 -2
  84. package/dist/src/doctor/doctor-backlog.js +546 -49
  85. package/dist/src/doctor/doctor-checks.js +534 -56
  86. package/dist/src/doctor/doctor-holds.js +193 -53
  87. package/dist/src/doctor/doctor-install.js +30 -0
  88. package/dist/src/doctor/doctor-launch-services.js +2 -2
  89. package/dist/src/doctor/doctor-model-checks.js +549 -157
  90. package/dist/src/doctor/doctor-notify.js +27 -0
  91. package/dist/src/doctor/doctor-orphans.js +10 -9
  92. package/dist/src/doctor/doctor-render.js +36 -9
  93. package/dist/src/doctor/doctor.js +80 -22
  94. package/dist/src/doctor/image-probe.js +244 -0
  95. package/dist/src/doctor/probe.js +64 -0
  96. package/dist/src/events/event-format.js +226 -102
  97. package/dist/src/events/event-labels.js +116 -0
  98. package/dist/src/events/event-legacy.js +140 -0
  99. package/dist/src/events/event-read.js +58 -22
  100. package/dist/src/events/event-window.js +27 -7
  101. package/dist/src/events/events.js +18 -14
  102. package/dist/src/events/notify.js +137 -38
  103. package/dist/src/failure/failure-cluster.js +12 -9
  104. package/dist/src/failure/failure-data.js +96 -48
  105. package/dist/src/failure/failure-render.js +69 -58
  106. package/dist/src/failure/failure-state-change.js +33 -26
  107. package/dist/src/failure/time-spend.js +79 -59
  108. package/dist/src/files/file-queue.js +16 -4
  109. package/dist/src/files/files.js +18 -6
  110. package/dist/src/files/json-object.js +18 -3
  111. package/dist/src/files/tail.js +1 -1
  112. package/dist/src/gates/bootstrap-gates.js +5 -4
  113. package/dist/src/gates/budget-gates.js +250 -34
  114. package/dist/src/gates/disk-gate.js +18 -18
  115. package/dist/src/gates/gate-polls.js +117 -69
  116. package/dist/src/gates/maintenance-quota.js +29 -29
  117. package/dist/src/gates/pause-gates.js +11 -11
  118. package/dist/src/gates/readiness.js +2 -1
  119. package/dist/src/gates/role-cap-gates.js +16 -21
  120. package/dist/src/gates/startup-gate.js +75 -31
  121. package/dist/src/gates/streak-gate.js +18 -16
  122. package/dist/src/git/commit-message.js +12 -5
  123. package/dist/src/git/git-diff.js +16 -16
  124. package/dist/src/git/git-run.js +8 -8
  125. package/dist/src/git/git.js +86 -55
  126. package/dist/src/git/slots-state.js +14 -5
  127. package/dist/src/git/worktree-pool.js +25 -18
  128. package/dist/src/git/worktree-use.js +15 -14
  129. package/dist/src/git/worktree.js +18 -18
  130. package/dist/src/gui/gui-args.js +10 -14
  131. package/dist/src/gui/gui-backlog-commands.js +273 -0
  132. package/dist/src/gui/gui-command.js +1 -1
  133. package/dist/src/gui/gui-config-commands.js +440 -0
  134. package/dist/src/gui/gui-endpoint-commands.js +16 -669
  135. package/dist/src/gui/gui-endpoint-common.js +49 -0
  136. package/dist/src/gui/gui-endpoints.js +116 -56
  137. package/dist/src/gui/gui-operator-commands.js +145 -0
  138. package/dist/src/gui/gui-prompt-commands.js +234 -0
  139. package/dist/src/gui/gui-server.js +28 -8
  140. package/dist/src/gui/gui-team-commands.js +164 -0
  141. package/dist/src/gui/http-body.js +17 -1
  142. package/dist/src/history/history-data.js +194 -98
  143. package/dist/src/history/history-render.js +76 -0
  144. package/dist/src/history/history.js +37 -58
  145. package/dist/src/inbox/inbox-attachments.js +75 -14
  146. package/dist/src/inbox/inbox-cancel.js +7 -5
  147. package/dist/src/inbox/inbox-edit.js +1 -1
  148. package/dist/src/inbox/inbox-submit.js +44 -4
  149. package/dist/src/inbox/inbox-top.js +83 -0
  150. package/dist/src/inbox/inbox.js +8 -2
  151. package/dist/src/inbox/pending-prompt.js +1 -1
  152. package/dist/src/inbox/prompt-commands.js +170 -80
  153. package/dist/src/init/init-templates.js +16 -14
  154. package/dist/src/init/init.js +12 -11
  155. package/dist/src/loop/leftover.js +28 -27
  156. package/dist/src/loop/loop-fallback.js +2 -2
  157. package/dist/src/loop/loop-pi.js +106 -52
  158. package/dist/src/loop/loop-recovery.js +15 -15
  159. package/dist/src/loop/loop-state.js +139 -13
  160. package/dist/src/loop/loop-tick.js +194 -86
  161. package/dist/src/loop/loop.js +72 -36
  162. package/dist/src/loop/model-fallback.js +3 -2
  163. package/dist/src/loop/revision.js +16 -7
  164. package/dist/src/merge/backlog-conflicts.js +155 -0
  165. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
  166. package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
  167. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
  168. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
  169. package/dist/src/merge/merge-drain.js +212 -0
  170. package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
  171. package/dist/src/merge/merge-pipeline.js +99 -0
  172. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  173. package/dist/src/merge/merge-queue-render.js +75 -0
  174. package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
  175. package/dist/src/merge/merge-slot.js +340 -0
  176. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
  177. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
  178. package/dist/src/merge/merge-verify.js +212 -0
  179. package/dist/src/notes-cli.js +97 -0
  180. package/dist/src/operator/operator-commands.js +154 -47
  181. package/dist/src/operator/operator-intent.js +87 -49
  182. package/dist/src/operator/operator-requests.js +52 -52
  183. package/dist/src/operator/retire.js +62 -27
  184. package/dist/src/operator/revert.js +71 -0
  185. package/dist/src/orchestrator/orchestrator-launch.js +74 -14
  186. package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
  187. package/dist/src/orchestrator/orchestrator.js +158 -100
  188. package/dist/src/orchestrator/retention.js +29 -12
  189. package/dist/src/orchestrator/startup-migration-steps.js +51 -0
  190. package/dist/src/orchestrator/startup-migrations.js +40 -0
  191. package/dist/src/paths.js +128 -37
  192. package/dist/src/pi/pi-args.js +26 -3
  193. package/dist/src/pi/pi-backend.js +15 -23
  194. package/dist/src/pi/pi-bin.js +18 -12
  195. package/dist/src/pi/pi-event-line.js +17 -1
  196. package/dist/src/pi/pi-failure.js +87 -0
  197. package/dist/src/pi/pi-models.js +126 -38
  198. package/dist/src/pi/pi-run-result.js +1 -1
  199. package/dist/src/pi/pi-settings.js +86 -0
  200. package/dist/src/pi/pi-stream.js +131 -107
  201. package/dist/src/pi/pi-watchdogs.js +47 -43
  202. package/dist/src/pi-extension/bounded-output.js +20 -5
  203. package/dist/src/pi-extension/context-budget.js +3 -1
  204. package/dist/src/pi-extension/context-shake.js +54 -21
  205. package/dist/src/pi-extension/context-window.js +37 -0
  206. package/dist/src/pi-extension/full-output.js +2 -2
  207. package/dist/src/pi-extension/strip-thinking.js +80 -0
  208. package/dist/src/pi-extension/trim-system-prompt.js +38 -0
  209. package/dist/src/process/launch-services.js +11 -10
  210. package/dist/src/process/process-table.js +1 -1
  211. package/dist/src/process/process.js +7 -7
  212. package/dist/src/process/run-marker.js +29 -6
  213. package/dist/src/process/supervisor.js +47 -25
  214. package/dist/src/prompt/dialect.js +30 -10
  215. package/dist/src/prompt/gate-prompts.js +170 -67
  216. package/dist/src/prompt/principles.js +1 -0
  217. package/dist/src/prompt/prompt-followup.js +35 -20
  218. package/dist/src/prompt/prompt.js +249 -91
  219. package/dist/src/redeploy/redeploy-policy.js +10 -10
  220. package/dist/src/redeploy/redeploy.js +6 -4
  221. package/dist/src/redeploy/redeployer.js +63 -55
  222. package/dist/src/redeploy/self-reload.js +1 -1
  223. package/dist/src/report/report-data.js +264 -255
  224. package/dist/src/report/report-fold.js +353 -0
  225. package/dist/src/report/report-render.js +270 -33
  226. package/dist/src/request-timeouts.js +1 -1
  227. package/dist/src/review/known-flakes.js +34 -14
  228. package/dist/src/review/protected-edits.js +33 -0
  229. package/dist/src/review/review-followup.js +16 -4
  230. package/dist/src/review/review-precheck.js +14 -13
  231. package/dist/src/review/review-verdict.js +14 -0
  232. package/dist/src/review/review.js +81 -39
  233. package/dist/src/review/secret-leaks.js +127 -0
  234. package/dist/src/review/suite-rerun.js +4 -3
  235. package/dist/src/roles/legacy-role-ids.js +21 -0
  236. package/dist/src/roles/legacy-role-migration.js +62 -0
  237. package/dist/src/roles/loop-ids.js +19 -2
  238. package/dist/src/roles/role-catalog.js +125 -135
  239. package/dist/src/roles/role-guidance.js +64 -31
  240. package/dist/src/roles/role-labels.js +10 -0
  241. package/dist/src/roles/role-render.js +194 -18
  242. package/dist/src/roles/role-view.js +216 -17
  243. package/dist/src/roles/roles.js +18 -15
  244. package/dist/src/scheduling/backoff.js +29 -19
  245. package/dist/src/scheduling/claims.js +25 -9
  246. package/dist/src/scheduling/once-round.js +11 -11
  247. package/dist/src/scheduling/quiet-hours.js +25 -32
  248. package/dist/src/scheduling/scheduling.js +43 -24
  249. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  250. package/dist/src/status/status-budget.js +115 -0
  251. package/dist/src/status/status-data.js +77 -184
  252. package/dist/src/status/status-inbox.js +47 -0
  253. package/dist/src/status/status-install.js +27 -0
  254. package/dist/src/status/status-polls.js +68 -33
  255. package/dist/src/status/status-roles.js +105 -0
  256. package/dist/src/{fleet → team}/error-storm.js +10 -10
  257. package/dist/src/{fleet → team}/failure-spread.js +8 -8
  258. package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
  259. package/dist/src/{fleet → team}/reclaim.js +5 -4
  260. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
  261. package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
  262. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
  263. package/dist/src/text/datetime.js +42 -2
  264. package/dist/src/text/format.js +10 -1
  265. package/dist/src/text/markdown.js +1 -1
  266. package/dist/src/text/phrases.js +35 -34
  267. package/dist/src/text/suggest.js +3 -2
  268. package/dist/src/text/text.js +2 -2
  269. package/dist/src/tick/qa-coverage.js +2 -1
  270. package/dist/src/tick/stage-check.js +147 -26
  271. package/dist/src/tick/telemetry-digest.js +10 -9
  272. package/dist/src/tick/tick-apply.js +101 -72
  273. package/dist/src/tick/tick-detail-data.js +32 -18
  274. package/dist/src/tick/tick-detail.js +62 -21
  275. package/dist/src/tick/tick-finalize.js +29 -22
  276. package/dist/src/tick/tick-outcome.js +2 -1
  277. package/dist/src/tick/tick-prompt.js +94 -26
  278. package/dist/src/tick/tick-resume.js +29 -13
  279. package/dist/src/tick/tick-stage.js +91 -47
  280. package/dist/src/tick/tick-timing.js +60 -59
  281. package/dist/src/tick/tick-usage.js +53 -23
  282. package/dist/src/tick/tick-verdict.js +76 -10
  283. package/dist/src/ui/badges.js +118 -67
  284. package/dist/src/ui/gui/gui-client-boot.js +209 -30
  285. package/dist/src/ui/gui/gui-client-composer.js +102 -23
  286. package/dist/src/ui/gui/gui-client-drawer.js +330 -75
  287. package/dist/src/ui/gui/gui-client-history.js +96 -38
  288. package/dist/src/ui/gui/gui-client-labels.js +24 -0
  289. package/dist/src/ui/gui/gui-client-loops.js +94 -52
  290. package/dist/src/ui/gui/gui-client-markdown.js +1 -0
  291. package/dist/src/ui/gui/gui-client-model.js +63 -43
  292. package/dist/src/ui/gui/gui-client-operator.js +129 -39
  293. package/dist/src/ui/gui/gui-client-pending.js +36 -32
  294. package/dist/src/ui/gui/gui-client-report.js +339 -46
  295. package/dist/src/ui/gui/gui-client-settings.js +148 -14
  296. package/dist/src/ui/gui/gui-client-sound.js +2 -1
  297. package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
  298. package/dist/src/ui/gui/gui-client.js +26 -20
  299. package/dist/src/ui/gui/gui-page.js +19 -19
  300. package/dist/src/ui/gui/gui-styles.js +12 -4
  301. package/dist/src/ui/progress-data.js +5 -3
  302. package/dist/src/ui/status-model.js +184 -122
  303. package/dist/src/ui/status-payload.js +119 -32
  304. package/dist/src/ui/status-render.js +122 -52
  305. package/dist/src/ui/team-alerts.js +341 -0
  306. package/dist/src/ui/tick-progress-model.js +14 -14
  307. package/dist/src/ui/tone.js +11 -8
  308. package/dist/src/ui/transcript-tail.js +1 -0
  309. package/dist/src/ui/transcript.js +23 -16
  310. package/dist/src/ui/tui/tui-app.js +1 -0
  311. package/dist/src/ui/tui/tui-backlog.js +85 -11
  312. package/dist/src/ui/tui/tui-editor-answer.js +97 -0
  313. package/dist/src/ui/tui/tui-editor-attach.js +107 -0
  314. package/dist/src/ui/tui/tui-editor-brief.js +107 -0
  315. package/dist/src/ui/tui/tui-editor-budget.js +155 -0
  316. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  317. package/dist/src/ui/tui/tui-editor-entry.js +230 -0
  318. package/dist/src/ui/tui/tui-editor-interval.js +122 -0
  319. package/dist/src/ui/tui/tui-editor-notes.js +114 -0
  320. package/dist/src/ui/tui/tui-editor-pause.js +266 -0
  321. package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
  322. package/dist/src/ui/tui/tui-editor-queued.js +98 -0
  323. package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
  324. package/dist/src/ui/tui/tui-editor-retire.js +83 -0
  325. package/dist/src/ui/tui/tui-editor-search.js +73 -0
  326. package/dist/src/ui/tui/tui-editor-session.js +97 -0
  327. package/dist/src/ui/tui/tui-editors.js +152 -0
  328. package/dist/src/ui/tui/tui-frame.js +67 -55
  329. package/dist/src/ui/tui/tui-history.js +12 -7
  330. package/dist/src/ui/tui/tui-input.js +49 -44
  331. package/dist/src/ui/tui/tui-keymap.js +1 -0
  332. package/dist/src/ui/tui/tui-keys.js +91 -1704
  333. package/dist/src/ui/tui/tui-pane-activity.js +99 -0
  334. package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
  335. package/dist/src/ui/tui/tui-pane-brief.js +30 -0
  336. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  337. package/dist/src/ui/tui/tui-pane-health.js +124 -0
  338. package/dist/src/ui/tui/tui-pane-history.js +316 -0
  339. package/dist/src/ui/tui/tui-pane-pending.js +74 -0
  340. package/dist/src/ui/tui/tui-pane-queued.js +143 -0
  341. package/dist/src/ui/tui/tui-pane-report.js +122 -0
  342. package/dist/src/ui/tui/tui-pane-session.js +101 -0
  343. package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
  344. package/dist/src/ui/tui/tui-pane.js +66 -28
  345. package/dist/src/ui/tui/tui-panes.js +294 -0
  346. package/dist/src/ui/tui/tui.js +45 -25
  347. package/dist/src/verdict/fix-claim.js +141 -38
  348. package/dist/src/verdict/no-change.js +19 -0
  349. package/dist/src/verdict/refusal.js +92 -8
  350. package/dist/src/verdict/reply-contract.js +17 -5
  351. package/dist/src/version.js +3 -2
  352. package/package.json +4 -2
  353. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  354. package/dist/src/landing/backlog-conflicts.js +0 -116
  355. package/dist/src/landing/landing-drain.js +0 -204
  356. package/dist/src/landing/landing-pipeline.js +0 -96
  357. package/dist/src/landing/landing-queue-render.js +0 -31
  358. package/dist/src/landing/landing-slot.js +0 -246
  359. package/dist/src/landing/landing-vetting.js +0 -196
  360. package/dist/src/pi-extension/role-notes.js +0 -40
  361. package/dist/src/ui/fleet-alerts.js +0 -174
@@ -1,13 +1,14 @@
1
+ // vocabulary: plain
1
2
  import { shortSha } from "../text/format.js";
2
- import { TEST_RUNNER_RULE, dateLine } from "./prompt.js";
3
+ import { KILL_BY_PID_RULE, OLDER_NAMES_SENTENCE, TEST_RUNNER_RULE, dateLine } from "./prompt.js";
3
4
  import { NEEDS_REPLAN_NOTE, NEEDS_REVIEW_NOTE } from "../roles/role-guidance.js";
4
5
  import { describeCheck } from "../build/build-check-report.js";
5
6
  import { formatTimestamp } from "../text/datetime.js";
6
7
  import { numberedList } from "../text/markdown.js";
7
8
  import { SUMMARY_BLOCK, VERDICT_ENDING, NOTHING_TO_DO } from "../verdict/reply-contract.js";
8
- /** Prompts for the landing gate's pi runs — the runs the merge/review pipeline starts, not the
9
+ /** Prompts for the merge gate's pi runs — the runs the merge/review pipeline starts, not the
9
10
  * role loops' authoring ticks (those live in prompt.ts): conflict resolution after a rebase
10
- * (landing-merge.ts), the adversarial pre-merge review (review.ts), and the notes that carry the
11
+ * (merge-to-main.ts), the adversarial pre-merge review (review.ts), and the notes that carry the
11
12
  * gate's verdicts back to the loop that authored the change (loop-tick.ts, tick-prompt.ts,
12
13
  * main-red.ts). The machine-detectable half of the gate's reply contract — verdict constants
13
14
  * and detection for parsing a reviewer's VERDICT line — lives in reply-contract.ts. */
@@ -37,38 +38,67 @@ function conflictIntentBlocks(intent) {
37
38
  lines.push(`(${intent.mainOmitted} more not shown)`);
38
39
  return lines.join("\n");
39
40
  }
41
+ /** One conflicted file's bullet: its path, then its conflict hunks' line ranges when known
42
+ * (merge-git.ts's conflictedFileHunks). A plain path, or a file with no hunks found, is the bare
43
+ * bullet. */
44
+ function conflictedFileLine(file) {
45
+ if (typeof file === "string")
46
+ return `- ${file}`;
47
+ if (file.hunks.length === 0)
48
+ return `- ${file.path}`;
49
+ const ranges = file.hunks.map((h) => `${h.start}-${h.end}`).join(", ");
50
+ const more = file.more > 0 ? `, and ${file.more} more` : "";
51
+ return `- ${file.path} (conflict hunks at lines ${ranges}${more})`;
52
+ }
53
+ /** True when any conflicted file carries hunk ranges, so the prompt can point at them. */
54
+ function listsHunks(files) {
55
+ return files.some((f) => typeof f !== "string" && f.hunks.length > 0);
56
+ }
40
57
  /** The prompt for resolving merge conflicts left in a loop's worktree. `check` — the project's
41
58
  * resolved check, as the tick prompt threads it — is named so the resolver knows which command
42
59
  * the harness will run (told only to keep the tests passing, the 2026-10-04 resolver guessed
43
60
  * `npx vitest run` in a node:test repo, BUGS.md 2026-10-05), and the rules tell it not to run
44
- * that check itself: landing-merge.ts's verifyLanding runs it on the resolved tree before
45
- * anything lands, and the resolver runs inside the merge slot, so its own copy of the run held
46
- * every queued landing. On 2026-10-12 the resolvers' own suite runs were 86% of their wall time
61
+ * that check itself: merge-to-main.ts's verifyMerge runs it on the resolved tree before
62
+ * anything merges, and the resolver runs inside the merge slot, so its own copy of the run held
63
+ * every queued merge. On 2026-10-12 the resolvers' own suite runs were 86% of their wall time
47
64
  * (951 of 1,104 s over seven runs; one spent 6 min re-testing a tree identical to main).
48
65
  * Undefined keeps the generic sentence and adds no such rule: with no declared check the
49
66
  * harness verifies nothing. `intent` — this branch's own
50
67
  * commit message and the main commits that touched the conflicted files since the merge-base —
51
68
  * shows the resolver what both sides meant instead of only the markers; omitted, the prompt is
52
- * unchanged. `today` pins the date line (prompt.ts's dateLine) for tests; omitted, it is the
53
- * local day. */
69
+ * unchanged. A file given with its conflict hunks' line ranges (conflictedFileHunks) is listed
70
+ * with them, and the rules then send the resolver to those ranges: told only to grep the
71
+ * markers, the 2026-10-17..19 resolvers still opened the conflicted PLANS.md at line 1, one
72
+ * tool-output-capped read after another, while every queued merge waited. A plain path lists
73
+ * the file alone. `today` pins the date line (prompt.ts's dateLine) for tests; omitted, it is
74
+ * the local day. */
54
75
  export function buildConflictPrompt(roleId, files, check, intent, today) {
55
76
  const noCheckRunRule = check
56
77
  ? `
57
- - Do not run ${describeCheck(check)} (the project's declared check) or the full test suite: the
58
- harness runs that check on the resolved tree before it lands, and every queued landing waits
78
+ - Do not run ${describeCheck(check)} (the project's declared check) or the full test suite:
79
+ tumwater runs that check on the resolved tree before it merges, and every queued merge waits
59
80
  on this run. If you combined code from both sides, a compile or the one or two test files
60
81
  that cover it is enough.`
61
82
  : "";
62
83
  const intentBlocks = intent ? conflictIntentBlocks(intent) : "";
63
84
  const removalCitation = intent ? ` (see "What main changed in these files" above)` : "";
64
- return `You are the "${roleId}" loop of tumwater, an autonomous development harness. A rebase of
85
+ const readRule = listsHunks(files)
86
+ ? `- Read only the conflicted files and what they directly reference, not the codebase at large.
87
+ Read each conflict hunk listed above as its line range plus about five lines of context on
88
+ each side; never read a conflicted file from the top, where one read of a long-lined file
89
+ fills the tool-output limit. Editing a hunk shifts the line numbers below it, so resolve the
90
+ last hunk first, or re-run \`grep -n '<<<<<<<' FILE\`, which also finds hunks past the listed
91
+ ones.`
92
+ : `- Read only the conflicted files and what they directly reference (\`grep -n '<<<<<<<' FILE\`
93
+ finds each marker; read around it in ranges) — not the codebase at large.`;
94
+ return `You are the "${roleId}" role on this project's tumwater team — tumwater runs a team of AI roles that develop the project. ${OLDER_NAMES_SENTENCE} A rebase of
65
95
  your work branch onto main stopped on conflicts; the conflict markers are sitting in the
66
96
  worktree now. Resolve them.
67
97
 
68
98
  ${dateLine(today)}
69
99
 
70
100
  Conflicted files:
71
- ${files.map((f) => `- ${f}`).join("\n")}
101
+ ${files.map(conflictedFileLine).join("\n")}
72
102
  ${intentBlocks ? intentBlocks + "\n" : ""}
73
103
  Resolve every conflict marker (<<<<<<<, =======, >>>>>>>) by combining the intent of BOTH sides:
74
104
  "ours" is this branch's change, "theirs" is the latest main. Do not simply pick one side unless
@@ -76,20 +106,20 @@ the two changes are genuinely alternatives. Keep the project building and its te
76
106
 
77
107
  Rules for this run:
78
108
  - Edit files only. Never run any git command that changes state (no add, commit, merge, rebase,
79
- reset, checkout) — the harness concludes the rebase for you. Reading git state is fine.
80
- - Read only the conflicted files and what they directly reference (\`grep -n '<<<<<<<' FILE\`
81
- finds each marker; read around it in ranges) — not the codebase at large.
109
+ reset, checkout) — tumwater concludes the rebase for you. Reading git state is fine.
110
+ ${readRule}
82
111
  - When main has deliberately removed or replaced what the branch edits (a revert, a rewrite of
83
112
  the same code), the branch's edit is dropped: resolve toward main's version — the branch's
84
113
  change is re-derived on top of current main by its author if it still matters. Never merge
85
114
  the branch's version back in over main's deliberate removal${removalCitation}.
86
115
  - Never touch the .tumwater directory or tumwater.json.
87
116
  ${TEST_RUNNER_RULE}${noCheckRunRule}
117
+ - ${KILL_BY_PID_RULE}
88
118
  - When a conflicted file is a markdown backlog file (PLANS.md, BUGS.md, QUESTIONS.md), its
89
119
  \`## \` section headings are structure, not text: the result keeps exactly one of each
90
120
  \`## \` heading, and every \`### \` entry sits under the section its own side put it in —
91
121
  a newly planned feature stays under \`## Planned\` even when the other side moved entries
92
- around it.
122
+ around it. \`grep -n '^## ' FILE\` lists those headings with their line numbers.
93
123
  - When every marker is resolved and the project is consistent, just stop.`;
94
124
  }
95
125
  /** The prompt for the adversarial pre-merge review gate: a fresh-session pi run that sees only the
@@ -99,29 +129,29 @@ ${TEST_RUNNER_RULE}${noCheckRunRule}
99
129
  * green on this exact tree (e.g. "`npm run test` passed"), so the reviewer spends its run on what a
100
130
  * green suite cannot show instead of re-running it. The no-re-run instruction is its own line in
101
131
  * the rules list, not a clause in the context paragraph: stated there, reviewers read past it and
102
- * re-ran the suite in scratch copies under /tmp while holding the landing slot (BUGS.md
132
+ * re-ran the suite in scratch copies under /tmp while holding the merge slot (BUGS.md
103
133
  * 2026-09-23). It appears only when a verified result exists: after a timed-out, killed, or skipped
104
134
  * pre-check no green run stands behind the tree, and a reviewer running the suite itself is doing
105
135
  * its job. `baseRev` is the frozen revision the diff is measured against (changeBaseRev's
106
136
  * merge-base, resolved when the gate builds the diff): the prompt names it and tells the reviewer
107
- * to compare against it, never `main`, because other loops land on main while the review runs
137
+ * to compare against it, never `main`, because other loops merge to main while the review runs
108
138
  * (BUGS.md 2026-10-06). Omitted, the prompt keeps the generic "ahead of main" wording. `today` pins
109
139
  * the date line (prompt.ts's dateLine) for tests; omitted, it is the local day. The text must
110
140
  * contain the literal "VERDICT:" exactly twice — the two advertised forms — because a prompt
111
- * test derives the accepted forms from it. `priorReview` is present only when this landing is a
141
+ * test derives the accepted forms from it. `priorReview` is present only when this merge is a
112
142
  * revision of a previously rejected change (plans/revise-rejected.md part 2/2): it names the round
113
143
  * and shows the prior objections plus the interdiff so the re-review checks each one first.
114
144
  * Omitted, the prompt is unchanged from a fresh change's. */
115
145
  export function buildReviewPrompt(diff, summary, commitBody, principles, highFriction, verifiedByHarness, baseRev, today, priorReview) {
116
146
  const parts = [
117
- `You are an adversarial code reviewer for tumwater, an autonomous development harness. A
118
- loop's change is about to be merged to main; you decide whether it may land. You have no context
147
+ `You are an adversarial code reviewer for tumwater, which runs a team of AI roles on this project. ${OLDER_NAMES_SENTENCE} A
148
+ role's change is about to be merged to main; you decide whether it may merge. You have no context
119
149
  from the authoring run — judge only what is in front of you, and assume nothing until you have
120
150
  checked it.`,
121
151
  dateLine(today),
122
152
  ];
123
153
  if (highFriction)
124
- parts.push(`This change was flagged HIGH-FRICTION by the harness: its authoring run burned far more
154
+ parts.push(`This change was flagged HIGH-FRICTION by tumwater: its authoring run burned far more
125
155
  assistant turns or wall-clock time than this project's thresholds. Difficulty is a signal that
126
156
  the work may not fit the system — apply extra scrutiny to whether the change should exist at all,
127
157
  not just whether it is correct.`);
@@ -135,24 +165,27 @@ not just whether it is correct.`);
135
165
  of the prior review's numbered objections FIRST — an unresolved one is a rejection on its own —
136
166
  then review the whole diff as usual. List only the still-unresolved objections as your numbered
137
167
  reasons; state that a prior objection is resolved in prose, never as a numbered reason, so the
138
- recorded rejection is not read as the resolution itself. The prior objections were:\n${list}\nThe interdiff
168
+ recorded rejection is not read as the resolution itself. When a numbered reason restates one of
169
+ the prior objections, start it with the tag (prior N) where N is that objection's number in the
170
+ prior list, and leave a newly raised objection untagged — the tag is how tumwater tells a repeat
171
+ from a new finding. The prior objections were:\n${list}\nThe interdiff
139
172
  between the rejected version and this revision (what the revision changed, main's own movement
140
173
  excluded):\n<interdiff>\n${priorReview.interdiff}\n</interdiff>`);
141
174
  }
142
175
  if (verifiedByHarness)
143
- parts.push(`The harness already ran the project's own check on this exact tree and it passed:
176
+ parts.push(`Tumwater already ran the project's own check on this exact tree and it passed:
144
177
  ${verifiedByHarness}. Spend your run on what a green check cannot show: wrong behavior the tests
145
178
  never exercise, claims the diff does not back, work left half-done.`);
146
179
  if (principles) {
147
180
  parts.push(`Design principles this project holds — your review standard; a violation of one is a finding:\n<principles>\n${principles}\n</principles>`);
148
181
  }
149
- // The base the diff is measured from, named in the prompt: main moves while a landing is vetted,
182
+ // The base the diff is measured from, named in the prompt: main moves while a merge is approved,
150
183
  // and a reviewer that double-checks scope with `git diff main` reads a newer merge, reversed, as
151
184
  // part of the change (BUGS.md 2026-10-06). Sentence only when a base is given; the callers that
152
185
  // omit it (unit tests of the prompt's other clauses) keep the generic wording.
153
186
  const diffBase = baseRev ? `measured against ${shortSha(baseRev)}` : "everything the branch is ahead of main";
154
187
  const baseClause = baseRev
155
- ? ` It is measured against ${shortSha(baseRev)}; other loops land on main while you review, so compare against ${shortSha(baseRev)}, never against \`main\`, and treat the provided diff as authoritative for which files this change touches.`
188
+ ? ` It is measured against ${shortSha(baseRev)}; other roles merge on main while you review, so compare against ${shortSha(baseRev)}, never against \`main\`, and treat the provided diff as authoritative for which files this change touches.`
156
189
  : "";
157
190
  const truncationBase = baseRev ? shortSha(baseRev) : "main";
158
191
  parts.push(`The full diff this merge will land (${diffBase}):\n<diff>\n${diff}\n</diff>`);
@@ -160,7 +193,7 @@ never exercise, claims the diff does not back, work left half-done.`);
160
193
  // something, but a copy made to run the suite is the re-run this line forbids.
161
194
  const noRerunRule = verifiedByHarness
162
195
  ? `
163
- - Do not re-run the check named above or the project's full test suite: the harness's green run
196
+ - Do not re-run the check named above or the project's full test suite: tumwater's green run
164
197
  is the verified result. Copying the tree elsewhere to run it — rsync or cp into a temp
165
198
  directory, reinstalling dependencies there (for an npm project, \`npm ci\`; otherwise its
166
199
  equivalent) — counts as re-running it. Running one
@@ -186,7 +219,7 @@ Check, in this order:
186
219
  1. Does the diff do exactly what the summary and WHY claim — no more, no less? An unclaimed
187
220
  change is a finding.
188
221
  2. Are the VERIFIED claims consistent with the diff (commands, files, observations)? A claim you
189
- can disprove is a rejection. Suite counts are the harness's own attestation — they appear in
222
+ can disprove is a rejection. Suite counts are tumwater's own attestation — they appear in
190
223
  the passed-check line above when the check ran green — so a count missing from VERIFIED is
191
224
  not a finding, and neither is an author-stated count that differs from it: judge whether the
192
225
  tests exist and exercise the change (check 3), not the author's arithmetic.
@@ -208,6 +241,7 @@ Rules for this run:
208
241
  verdict below. Reading files and git history is fine; a scratch copy under the system temp
209
242
  directory is fine when you need to run something.${noRerunRule}
210
243
  ${TEST_RUNNER_RULE}
244
+ - ${KILL_BY_PID_RULE}
211
245
  - Weigh the change against its stated purpose; do not approve work you did not actually check.
212
246
  - End your reply with exactly one line in this form:
213
247
  ${VERDICT_ENDING.replace(/\n/g, "\n ")}`);
@@ -226,7 +260,7 @@ export function buildReviewResumePrompt(baseRev, patchId) {
226
260
 
227
261
  You are resuming an adversarial review of a tumwater change: the previous attempt hit its time budget while still making progress, and this run continues that same reviewer session. Everything you already read — the diff and the files you checked — is in your context; do not re-read it.
228
262
 
229
- The change is unchanged since that attempt (patch-id ${patchId}) and is still measured against ${shortSha(baseRev)}. Other loops land on main while you review, so compare against ${shortSha(baseRev)}, never against \`main\`.
263
+ The change is unchanged since that attempt (patch-id ${patchId}) and is still measured against ${shortSha(baseRev)}. Other roles merge on main while you review, so compare against ${shortSha(baseRev)}, never against \`main\`.
230
264
 
231
265
  Finish the checks you had not completed, then end your reply with exactly one line in this form:
232
266
  ${VERDICT_ENDING.replace(/\n/g, "\n ")}`;
@@ -239,7 +273,7 @@ Finish the checks you had not completed, then end your reply with exactly one li
239
273
  * too yields nothing (BUGS.md 2026-09-29). It shares the review prompt's closing rule
240
274
  * (reply-contract.ts's VERDICT_ENDING), so the accepted forms cannot drift between the prompts. */
241
275
  export function buildVerdictRequestPrompt() {
242
- return `Your review reply above did not include the verdict line the harness parses, so
276
+ return `Your review reply above did not include the verdict line tumwater parses, so
243
277
  your judgment was lost. Reply now with ONLY the closing block — no tool calls, no other text:
244
278
  one line in this form:
245
279
  ${VERDICT_ENDING}`;
@@ -249,7 +283,7 @@ one line in this form:
249
283
  * asks it to finish the review from what it already read. The reviewer's only output channel
250
284
  * is the verdict, so the closing rule names both accepted forms like the review prompt does. */
251
285
  export function buildNoRerunPrompt(rerun) {
252
- return `You started a tool call the harness's pre-check already covered, so it must not run:
286
+ return `You started a tool call tumwater's pre-check already covered, so it must not run:
253
287
  ${rerun}
254
288
  The full check passed at this exact tree before your review began. Do not re-run it — not in
255
289
  this worktree, not in a scratch copy. Finish the review now from what you have already read:
@@ -257,20 +291,23 @@ reply with ONLY the closing block — no tool calls, no other text — one line
257
291
  ${VERDICT_ENDING}`;
258
292
  }
259
293
  /** The one-turn follow-up sent into a tick's OWN session (--continue) when the harness's
260
- * pre-queue self-check found deterministic faults the landing gate would reject. The session
261
- * still holds everything the run did, so the author fixes the faults before the change commits
262
- * and queues — the gate keeps its final say, and the fix costs one bounded turn instead of a
263
- * rejection cycle. A finding the author believes is wrong is answered in RISK rather than
264
- * forced away; the reply re-states the closing block because the harness re-derives the commit
265
- * message from it. Carries the tick prompt's no-git rule (the harness commits). */
294
+ * pre-queue self-check found problems likely to get the change rejected. Only some findings are
295
+ * the merge gate's own deterministic checks; the rest (path drift, plan dependencies) are
296
+ * heuristics, so the prompt never claims the gate rejects every one — claiming it did made an
297
+ * author sweep a rename through the backlog's history (2026-10-18). The session still holds
298
+ * everything the run did, so the author fixes the faults before the change commits and queues —
299
+ * 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). */
266
303
  export function buildStageFixPrompt(findings) {
267
- return `Your change is about to commit, but the harness's pre-queue self-check found faults the
268
- landing gate rejects deterministically:
304
+ return `Your change is about to commit, but tumwater's pre-queue self-check found problems the
305
+ merge gate or its review is likely to reject:
269
306
  ${numberedList(findings)}
270
307
  Fix each one in the worktree now with the smallest edit that resolves it, and keep everything else
271
308
  as it is. When a finding is wrong, say why in your RISK line instead of forcing a change that
272
309
  does not help. Do not run any git command that changes state (no add, commit, reset, checkout) —
273
- the harness commits for you.
310
+ tumwater commits for you.
274
311
  When done, reply with ONLY the closing block again — no tool calls, no other text, one line each:
275
312
  ${SUMMARY_BLOCK}`;
276
313
  }
@@ -295,15 +332,15 @@ export function buildRejectedReviewNote(review, role) {
295
332
  // notebook instead: it records the abandoned purpose and any instances that remain
296
333
  // unhandled, then takes a different theme on the next tick. Re-authoring the same purpose
297
334
  // from the same objections is what restarted the round and produced the long rejection
298
- // runs this note exists to end. The markdown-only replan note lands review-exempt, but a
299
- // landed change clears the standing rejection (clearSupersededRejection, tick-apply.ts), so
300
- // the instruction does not re-inject once the note has landed.
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.
301
338
  let finality = "";
302
339
  if (review.exhausted) {
303
340
  finality =
304
341
  role === "feature"
305
- ? ` This was the change's final revision round — 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 land that markdown-only change and nothing else so the plan loop can rewrite the plan. Otherwise, redo the change against current main.`
306
- : " This was the change's final revision round — the rejected diff and ref are gone, so do not re-author it. Record the abandoned purpose and any instances of it that remain unhandled in your notebook, then take a different theme on this tick: re-deriving the same purpose from the same objections is what produced the long rejection runs this note exists to end.";
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.";
307
344
  }
308
345
  return `Your previous change was rejected in review${context}:\n${list}\nAddress the objections or take a different approach.${finality}`;
309
346
  }
@@ -311,12 +348,13 @@ export function buildRejectedReviewNote(review, role) {
311
348
  * current main as uncommitted edits (plans/revise-rejected.md, loop/revision.ts's
312
349
  * applyRevision): this tick's one task is to revise the change already in the worktree, not
313
350
  * author a new one. The prior objections are numbered so the author can work through them; a
314
- * revision round past the first also reaches here. A pure function so its shape is pinned in
315
- * tests. */
316
- export function buildRevisionNote(review, round, limit) {
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) {
317
355
  const list = numberedList(review.reasons, NO_REASONS);
318
356
  return `Your previously rejected change is already in the worktree as uncommitted edits — this
319
- is revision ${round} of ${limit}. This tick's ONE task is to revise it; do not author anything else.
357
+ is revision ${round}. This run's ONE task is to revise it; do not author anything else.
320
358
  The review objections were:\n${list}
321
359
  Fix each numbered objection with the smallest edit that resolves it, and keep everything else as it
322
360
  is. When an objection is about a build-check failure in a test the change does not touch that you
@@ -324,22 +362,27 @@ cannot reproduce, say so in RISK and keep the change as it is. Rewrite the closi
324
362
  describes the WHOLE change as it now stands:
325
363
  ${SUMMARY_BLOCK}
326
364
  When an objection shows the change should not exist (its premise is disproven, it duplicates main,
327
- or it has no reachable benefit), end your reply with the line ${NOTHING_TO_DO} instead, and the
328
- harness drops the change.`;
365
+ or it has no reachable benefit), end your reply with the line ${NOTHING_TO_DO} instead, and
366
+ tumwater drops the change.`;
329
367
  }
330
368
  /** The note appended to a work instance's tick prompt when it holds a claim (plans/parallel-
331
369
  * work-instances.md "Claims", part 4/7): the harness has already picked its entry, so the
332
370
  * charter's choosing step is replaced. A pure function so its shape is pinned in tests; the
333
371
  * range is optional because a claim whose entry was already released still gets the note. */
334
- export function buildAssignmentNote(claim, range) {
372
+ export function buildAssignmentNote(claim, range, missingPaths = []) {
335
373
  const where = range ? ` (${claim.file} lines ${range.start}-${range.end})` : ` (${claim.file})`;
336
374
  const intro = claim.source === "staged"
337
375
  ? `Your change moved ONE backlog entry, so it is now your task, replacing your charter's "pick one" step:`
338
- : `The harness has assigned you ONE backlog entry, 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.`;
339
382
  return `<assigned-entry>
340
383
  ${intro}
341
384
  "${claim.title}"${where}.
342
- 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.
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}
343
386
  </assigned-entry>`;
344
387
  }
345
388
  /** The note appended to an unassigned work instance's prompt when siblings hold backlog entries
@@ -352,38 +395,74 @@ Sibling instances hold these backlog entries; do not pick, edit, or implement th
352
395
  ${titles.map((t) => `- "${t}"`).join("\n")}
353
396
  </held-entries>`;
354
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
+ }
355
429
  /** The note injected into a role's prompt after leftover recovery discarded its pinned change
356
430
  * at MERGE_CONFLICT_LIMIT (src/loop/leftover.ts): at the cap recovery ordinarily hands the
357
431
  * change back to its author with markers instead, so a discard means that lineage already used
358
- * its hand-back. The landing failures were harness-level, so without the note the author would
432
+ * its hand-back. The merge failures were harness-level, so without the note the author would
359
433
  * never learn its work is gone — or that redoing it from memory of the old diff would conflict
360
434
  * again. A pure function so its shape is pinned in tests. */
361
435
  export function buildConflictDiscardNote(summary, attempts) {
362
- return (`Your previous change ("${summary}") was discarded without landing: it conflicted with main ` +
436
+ return (`Your previous change ("${summary}") was discarded without merging: it conflicted with main ` +
363
437
  `${attempts} times and conflict resolution could not reconcile it. Main has moved on since you ` +
364
438
  `wrote it. If the change is still needed, redo it against current main, starting from main's ` +
365
439
  `current version of the files it touched rather than your earlier diff.`);
366
440
  }
367
441
  /** The note appended to a role's tick prompt when a change was handed back to it with the
368
- * conflict markers left in place (PLANS.md "Robust conflict landing, part 2/2"): the landing
442
+ * conflict markers left in place (PLANS.md "Robust conflict landing, part 2/2"): the merge
369
443
  * resolver could not settle it, so the author — who knows what the change was for — resolves it
370
- * over current main. `reason` says whether it was an approved change whose landing kept
444
+ * over current main. `reason` says whether it was an approved change whose merge kept
371
445
  * conflicting or a rejected change whose revision re-apply conflicted. The note names the
372
446
  * conflicted files and shows the same "What main changed in these files" intent block part 1/2
373
- * gives the resolver, so the author sees what main did without re-deriving it. A pure function
374
- * so its shape is pinned in tests. */
447
+ * gives the resolver, so the author sees what main did without re-deriving it. Files given with
448
+ * their hunk ranges are listed the way the resolver prompt lists them, with a line pointing the
449
+ * author at those ranges: a hand-back can leave markers in a 1.9 MB PLANS-DONE.md. A pure
450
+ * function so its shape is pinned in tests. */
375
451
  export function buildConflictHandbackNote(reason, conflicted, intent) {
376
- const why = reason === "landing"
377
- ? "Your previously approved change could not be landed: conflict resolution could not reconcile it with main"
452
+ const why = reason === "merge"
453
+ ? "Your previously approved change could not be merged: conflict resolution could not reconcile it with main"
378
454
  : "Your rejected change was being revised, but its diff no longer applies cleanly to main";
379
- const files = conflicted.length > 0 ? conflicted.map((f) => `- ${f}`).join("\n") : "- (none recorded)";
455
+ const files = conflicted.length > 0 ? conflicted.map(conflictedFileLine).join("\n") : "- (none recorded)";
456
+ const readHint = listsHunks(conflicted)
457
+ ? "\nRead each listed hunk's line range plus a few lines of context, not a conflicted file from the top."
458
+ : "";
380
459
  return `<conflict-handback>
381
460
  ${why}. Main moved under it, and the change is now in your worktree as uncommitted edits with the
382
461
  conflict markers left in place.
383
462
  Resolve every marker (<<<<<<<, =======, >>>>>>>) by combining the intent of BOTH sides, keeping
384
463
  main's deliberate removals, then re-run the project's declared check.
385
464
  Conflicted files:
386
- ${files}
465
+ ${files}${readHint}
387
466
 
388
467
  ${conflictIntentBlocks(intent)}
389
468
  </conflict-handback>`;
@@ -396,14 +475,38 @@ ${conflictIntentBlocks(intent)}
396
475
  * the suite red, so authoring anything else first is waste. A pure function so its shape is
397
476
  * pinned in tests; script/headline are optional because a red carries a failing script, but the
398
477
  * note must still read sensibly if it is ever absent. */
399
- export function buildMainRedNote(sha, script, headline) {
478
+ export function buildMainRedNote(sha, script, headline, tail) {
400
479
  const what = script
401
480
  ? `${script}${headline ? `: ${headline}` : ""}`
402
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
+ : "";
403
493
  return `<main-red>
404
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.
405
- This tick your job is to make main green: reproduce that failure, fix it (add a regression test where one is feasible), and land the fix.
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.
406
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.
407
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.
408
- </main-red>`;
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>`;
409
512
  }
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  import path from "node:path";
2
3
  import { readTextOrNull } from "../files/files.js";
3
4
  import { truncateWithNote } from "../text/text.js";
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  import { contextBudgetRule } from "./prompt.js";
2
3
  import { dialectFor } from "./dialect.js";
3
4
  import { NOTHING_TO_DO, REPLY_ENDINGS, SUMMARY_BLOCK } from "../verdict/reply-contract.js";
@@ -16,39 +17,39 @@ unusable, redo it — but stay on this task rather than picking a new one.`;
16
17
  * model re-read the whole tree and repeated identical reads of one file nine times. */
17
18
  export function buildResumePrompt(roleId, cause = "restart", agent) {
18
19
  const opening = cause === "cut-off"
19
- ? `Your previous run as the "${roleId}" loop ran out of context before it could finish, so the
20
- harness compacted the session and is continuing it now. Your worktree is exactly as you left it;
20
+ ? `Your previous run as the "${roleId}" role ran out of context before it could finish, so
21
+ tumwater compacted the session and is continuing it now. Your worktree is exactly as you left it;
21
22
  what you did so far is summarized above. Do NOT re-read the codebase: trust the summary and
22
23
  re-check only what you must, in ranges — at most ~10 tool calls of re-reading, and never the same
23
24
  file twice. Finish the SAME task with the smallest change that completes it. If it cannot be
24
25
  finished within a fraction of the window, scope it down to what is already complete and coherent,
25
26
  leave the project working, and stop.`
26
27
  : cause === "hung-tool"
27
- ? `The harness killed your previous run as the "${roleId}" loop because it made no progress long enough to trip its hang watchdog — almost always one tool call that hung (a command waiting on input, or a scan far wider than intended). That tool call is dead: do not re-run it unchanged. Your worktree is exactly as you left it, and this session carries everything you did so far — verify the effect of anything the killed call was supposed to produce before relying on it, and bound any long-running command (a time limit, a scoped path).
28
+ ? `Tumwater killed your previous run as the "${roleId}" role because it made no progress long enough to trip its hang watchdog — almost always one tool call that hung (a command waiting on input, or a scan far wider than intended). That tool call is dead: do not re-run it unchanged. Your worktree is exactly as you left it, and this session carries everything you did so far — verify the effect of anything the killed call was supposed to produce before relying on it, and bound any long-running command (a time limit, a scoped path).
28
29
 
29
30
  ${CONTINUE_TASK_BRIDGE}`
30
31
  : cause === "timeout"
31
- ? `Your previous run as the "${roleId}" loop reached the harness's tick time limit while
32
- it was still making progress — a slow run, not a failed one — so the harness preserved your
32
+ ? `Your previous run as the "${roleId}" role reached tumwater's run time limit while
33
+ it was still making progress — a slow run, not a failed one — so tumwater preserved your
33
34
  worktree and this session and is continuing them now. Finish the SAME task, but budget against
34
35
  that same limit: make the smallest change that completes the task coherently, verify it, and
35
36
  stop. Do not restart broad exploration the first run already finished; trust the work so far
36
37
  and build on it.`
37
38
  : cause === "budget-resumed"
38
- ? `The fleet's daily budget has reopened, and your previous run as the "${roleId}" loop
39
- started on the fallback model — the harness has moved this session back to the primary model and
39
+ ? `The team's daily budget has reopened, and your previous run as the "${roleId}" role
40
+ started on the fallback model — tumwater has moved this session back to the primary model and
40
41
  is continuing it now. Your worktree is exactly as you left it, and this session carries everything
41
42
  you did so far.
42
43
 
43
44
  ${CONTINUE_TASK_BRIDGE}`
44
45
  : cause === "rate-limit"
45
46
  ? `The claude.ai subscription usage window rejected your previous run as the "${roleId}"
46
- loop before it could finish, so the harness held every subscription claude seam until the window
47
+ role before it could finish, so tumwater held every subscription claude model job until the window
47
48
  reset. Your worktree is exactly as you left it, and this session carries everything you did so
48
- far. The window has since reset, and the harness is continuing this session now.
49
+ far. The window has since reset, and tumwater is continuing this session now.
49
50
 
50
51
  ${CONTINUE_TASK_BRIDGE}`
51
- : `The harness was restarted while you (the "${roleId}" loop) were mid-run. Your worktree
52
+ : `Tumwater was restarted while you (the "${roleId}" role) were mid-run. Your worktree
52
53
  is exactly as you left it, and this session carries everything you did so far. A tool call that
53
54
  was executing when the restart hit may not have finished — verify its effect before relying on it.
54
55
 
@@ -57,7 +58,7 @@ ${CONTINUE_TASK_BRIDGE}`;
57
58
  still apply, in particular:
58
59
  - Do exactly ONE focused task, then stop.
59
60
  ${contextBudgetRule(dialectFor(agent))}
60
- - Never create, amend, or revert git commits — the harness handles all git operations.
61
+ - Never create, amend, or revert git commits — tumwater handles all git operations.
61
62
  - Your last message is plain text — never a tool call or an announcement of a next step.
62
63
  ${REPLY_ENDINGS}`;
63
64
  }
@@ -66,33 +67,47 @@ ${REPLY_ENDINGS}`;
66
67
  * the full assembled tick prompt, plus the one fact a fresh session cannot know: an interrupted
67
68
  * run's uncommitted edits are sitting in the worktree. The worktree is NOT reset for a handoff,
68
69
  * so the bridge must send the new run to `git diff` before it starts its own work. */
69
- export function buildHandoffPrompt(roleId, tickPrompt, cause = "handoff") {
70
+ export function buildHandoffPrompt(roleId, tickPrompt, cause = "handoff",
71
+ /** Named facts an image-reroute handoff note needs that the cause alone cannot carry: the
72
+ * model the blind run was on and the image file it read. */
73
+ imageReroute) {
70
74
  const note = cause === "budget-capped"
71
- ? `An earlier API-billed claude run of the "${roleId}" loop hit its per-run budget cap before it
75
+ ? `An earlier API-billed claude run of the "${roleId}" role hit its per-run budget cap before it
72
76
  could finish; its uncommitted edits are in your worktree — review \`git diff\`, then finish or
73
- discard them. This run starts on the seam the daily budget left you.`
74
- : `An earlier run of the "${roleId}" loop on another model was interrupted; its
77
+ discard them. This run starts on the model job the daily budget left you.`
78
+ : cause === "image-reroute"
79
+ ? `An earlier run of the "${roleId}" role on ${imageReroute?.from ?? "another model"} stopped
80
+ when it read ${imageReroute?.path ?? "an image"}, because that model cannot read images; its
81
+ uncommitted edits are in your worktree — review \`git diff\`, then finish or discard them. This
82
+ run is on a model that CAN read images, so read the image again.`
83
+ : `An earlier run of the "${roleId}" role on another model was interrupted; its
75
84
  uncommitted edits are in your worktree — review \`git diff\`, then finish or discard them.`;
76
85
  return `${tickPrompt}\n\n${note}`;
77
86
  }
78
87
  /** The one-turn follow-up sent into a tick's OWN session (--continue) when the run changed files
79
- * but its reply carried no SUMMARY line — 73 of the first 670 commits landed as "<role> tick N"
88
+ * but its reply carried no SUMMARY line — 73 of the first 670 commits merged as "<role> tick N"
80
89
  * because of that, most of them the largest diffs in the repo. The session still holds everything
81
90
  * the run did, so one short reply recovers the subject and body the commit deserves; the caller
82
91
  * bounds the run tightly and falls back to a diff-derived subject if this too yields nothing. */
83
- export function buildSummaryRequestPrompt() {
92
+ export function buildSummaryRequestPrompt(overLength) {
93
+ if (overLength !== undefined) {
94
+ return `Your final reply's SUMMARY line is ${overLength} characters; the commit subject holds at most 72,
95
+ so tumwater would cut it mid-word. Reply now with ONLY the closing block, with the SUMMARY
96
+ restated in at most 72 characters — no tool calls, no other text, one line each:
97
+ ${SUMMARY_BLOCK}`;
98
+ }
84
99
  return `Your run changed files in the worktree, but your final reply
85
- did not include the required closing block, so the harness cannot describe the commit it is about
100
+ did not include the required closing block, so tumwater cannot describe the commit it is about
86
101
  to make. Reply now with ONLY that block — no tool calls, no other text, one line each:
87
102
  ${SUMMARY_BLOCK}`;
88
103
  }
89
104
  /** The note injected into a role's next FRESH tick prompt after its previous run(s) were cut
90
- * off at the context ceiling without landing anything (the loop gave up resuming — see
105
+ * off at the context ceiling without merging anything (the loop gave up resuming — see
91
106
  * tick-apply.ts's CUT_OFF_RESUME_LIMIT — or a cut-off director prompt is re-running). The only
92
107
  * cross-tick memory that the last attempt was too big for the window. */
93
108
  export function buildCutOffNote(streak) {
94
109
  const runs = streak === 1 ? "run" : `${streak} runs`;
95
- return `Your previous ${runs} as this loop ran out of context before landing anything. Pick a
110
+ return `Your previous ${runs} as this role ran out of context before merging anything. Pick a
96
111
  smaller, more targeted task this time and budget your reading: grep first, read in ranges, cap
97
112
  command output. If the smallest useful task still needs most of the codebase in view, reply
98
113
  ${NOTHING_TO_DO} instead of starting it.`;