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
@@ -0,0 +1,62 @@
1
+ import fs from "node:fs";
2
+ import { deleteRefIfAt, refSha } from "../git/git.js";
3
+ import { gitTry, runGit } from "../git/git-run.js";
4
+ import { branchName, mergeRefName, rejectedRefName, reviewSessionDir, roleInboxDir, roleNotesPath, sessionDir, statePath } from "../paths.js";
5
+ import { errorMessage } from "../text/text.js";
6
+ import { LEGACY_ROLE_IDS } from "./legacy-role-ids.js";
7
+ /** Move each retired role id's branch, refs and files to the new id (plans/terminology.md "Role
8
+ * names"). Returns one line per artifact moved or skipped. Nothing that exists under the new id
9
+ * is overwritten, each artifact fails alone, and pi and native logs stay put as history. */
10
+ export async function migrateLegacyRoleIds(root, legacy = LEGACY_ROLE_IDS) {
11
+ const notes = [];
12
+ for (const [oldId, newId] of Object.entries(legacy)) {
13
+ const attempt = async (what, move) => {
14
+ try {
15
+ const note = await move();
16
+ if (note !== null)
17
+ notes.push(note);
18
+ }
19
+ catch (err) {
20
+ notes.push(`could not move ${what}: ${errorMessage(err)}`);
21
+ }
22
+ };
23
+ await attempt("branch", async () => {
24
+ const from = branchName(oldId);
25
+ const to = branchName(newId);
26
+ if ((await refSha(root, `refs/heads/${from}`)) === null)
27
+ return null;
28
+ if ((await refSha(root, `refs/heads/${to}`)) !== null)
29
+ return `kept branch ${from} and ${to}: both exist`;
30
+ await runGit(root, ["branch", "-m", from, to]);
31
+ return `renamed branch ${from} to ${to}`;
32
+ });
33
+ for (const refFor of [mergeRefName, rejectedRefName]) {
34
+ const from = refFor(oldId);
35
+ const to = refFor(newId);
36
+ await attempt(from, async () => {
37
+ const sha = await refSha(root, from);
38
+ if (sha === null)
39
+ return null;
40
+ if ((await refSha(root, to)) !== null)
41
+ return `kept ${from} and ${to}: both exist`;
42
+ if ((await gitTry(root, "update-ref", to, sha)) === null)
43
+ throw new Error(`update-ref ${to} failed`);
44
+ await deleteRefIfAt(root, from, sha);
45
+ return `moved ${from} to ${to}`;
46
+ });
47
+ }
48
+ for (const pathFor of [statePath, roleNotesPath, roleInboxDir, sessionDir, reviewSessionDir]) {
49
+ const from = pathFor(root, oldId);
50
+ const to = pathFor(root, newId);
51
+ await attempt(from, async () => {
52
+ if (!fs.existsSync(from))
53
+ return null;
54
+ if (fs.existsSync(to))
55
+ return `kept ${from} and ${to}: both exist`;
56
+ fs.renameSync(from, to);
57
+ return `moved ${from} to ${to}`;
58
+ });
59
+ }
60
+ }
61
+ return notes;
62
+ }
@@ -9,8 +9,10 @@
9
9
  /** The base roles that may run more than one instance. */
10
10
  export const INSTANCE_ROLES = new Set(["feature", "bugfix"]);
11
11
  /** The instance-id shape: a base role, a dash, and an index of 2 or more (`-1` is not an
12
- * instance — the bare id already means instance 1). */
13
- const INSTANCE_RE = /^(feature|bugfix)-([2-9]|[1-9][0-9]+)$/;
12
+ * instance — the bare id already means instance 1). The source string is also interpolated into
13
+ * the dashboard script (gui-client-labels.ts). */
14
+ export const INSTANCE_ID_SOURCE = "^(feature|bugfix)-([2-9]|[1-9][0-9]+)$";
15
+ const INSTANCE_RE = new RegExp(INSTANCE_ID_SOURCE);
14
16
  /** The base role a loop id belongs to: `feature-2` → `feature`, `bugfix-10` → `bugfix`; any
15
17
  * other id — a bare role, a custom loop, `feature-1` — is returned unchanged. */
16
18
  export function baseRoleOf(id) {
@@ -58,3 +60,18 @@ export function loopIds(config) {
58
60
  }
59
61
  return ids;
60
62
  }
63
+ /** The pause-marker key for a loop id. Instance 1's loop id is the bare role id, which as a pause
64
+ * key would mean the whole role, so while the role runs several instances instance 1 pauses under
65
+ * `<role>-1`. `-1` is a pause key only, never a loop id. Every other id is its own key. */
66
+ export function instancePauseKey(config, loopId) {
67
+ return INSTANCE_ROLES.has(loopId) && configuredInstances(config, loopId) > 1
68
+ ? `${loopId}-1`
69
+ : loopId;
70
+ }
71
+ /** Does the per-role pause marker stop this loop id? True for its own id, its base role's id (a
72
+ * whole-role pause), or — for the bare id of an instance role — the `<role>-1` pause key. */
73
+ export function loopPaused(paused, loopId) {
74
+ return (paused.has(loopId) ||
75
+ paused.has(baseRoleOf(loopId)) ||
76
+ (INSTANCE_ROLES.has(loopId) && paused.has(`${loopId}-1`)));
77
+ }
@@ -1,11 +1,19 @@
1
+ // vocabulary: plain
1
2
  /** The role catalog: one built-in loop's identity and its role-specific "find something to do"
2
3
  * instruction text. Split out of roles.ts (2026-10-01, organize) so the catalog's embedded
3
4
  * prompt prose lives apart from the derived registries and classification helpers, which stayed
4
5
  * in roles.ts. Pure catalog data — the shared instruction fragments role prompts embed live in
5
6
  * role-guidance.ts (import-only downward, so prompt assembly depends on both without a cycle).
6
7
  * roles.ts re-exports ROLES and Role, so every existing importer keeps its import path. */
7
- import { backlogMoveGuidance, DECOMPOSITION_GUIDANCE, NEEDS_REPLAN_NOTE, NEEDS_REVIEW_NOTE, PLAN_SIZING, searchGuidance, VALIDATION_GAP_GUIDANCE, VALIDATION_GAP_TALLY, } from "./role-guidance.js";
8
+ import { backlogMoveGuidance, DECOMPOSITION_GUIDANCE, NEEDS_REPLAN_NOTE, NEEDS_REVIEW_NOTE, PLAN_SIZING, searchGuidance, VALIDATION_GAP_GUIDANCE, VALIDATION_GAP_QUERY, VALIDATION_GAP_TALLY, WAITING_FOR_FAILURE_NOTE, WAITING_FOR_FAILURE_PREFIX, } from "./role-guidance.js";
9
+ import { roleCommitGrep } from "./legacy-role-ids.js";
8
10
  import { NOTHING_TO_DO } from "../verdict/reply-contract.js";
11
+ /** The trailer the readme role's commits carry and its `git log --grep` reads back; git parses
12
+ * it, so it keeps this spelling. Interpolated because a comment inside the template would
13
+ * become prompt text. */
14
+ const README_TRAILER = "Run: readme #";
15
+ const README_TRAILER_GREP = "^(Run|Tick): readme #"; // vocabulary-ok: legacy name read from old refs or commits
16
+ const README_TRAILER_LEGACY = "Tick: readme #"; // vocabulary-ok: legacy name read from old refs or commits
9
17
  /** The opinionated role catalog. Every loop runs one role; a role's `find` text is
10
18
  * the role-specific "find something to do" half of the tick prompt.
11
19
  *
@@ -20,8 +28,9 @@ export const ROLES = [
20
28
  1. The prompt's <backlog-index> block lists PLANS.md's \`## Planned\` entries with each
21
29
  entry's line range; read the one you pick by that range.
22
30
  2. Pick one entry under \`## Planned\`, preferring ones marked ready or with a written plan.
23
- Skip entries the index block marks \`[blocked: …]\` (their prerequisite has not landed),
24
- \`[refused]\` or \`[needs review]\`. Skip plans whose entry carries a Refused note, and skip
31
+ Skip entries the index block marks \`[blocked: …]\` — \`[blocked: requires …]\` (a
32
+ prerequisite has not merged), \`[blocked: refused]\`, \`[blocked: needs review]\` or
33
+ \`[blocked: needs replan]\`. Skip plans whose entry carries a Refused note, and skip
25
34
  entries already carrying a **Needs review …** note or a ${NEEDS_REPLAN_NOTE}. If PLANS.md
26
35
  is empty or everything is done, there is nothing to do.
27
36
  3. Read only the chosen entry's line range and the code it names.
@@ -33,7 +42,7 @@ export const ROLES = [
33
42
  the entry's files-touched list and acceptance criteria, so land everything the entry
34
43
  promises, or update the entry to say what changed and why. When a plan's anchors no longer
35
44
  match the code, correct the entry in the same change instead of refusing.
36
- 6. Mark the plan done in PLANS.md — ${backlogMoveGuidance("PLANS.md", "## Planned", "## Done")}
45
+ 6. Mark the plan done in PLANS.md — ${backlogMoveGuidance("PLANS.md", "## Planned", "## Done", "PLANS-DONE.md")}
37
46
  7. Verify last, after the PLANS.md edit, so one run of the project's check covers everything
38
47
  you changed (see "Leave the project working" below).
39
48
  A plan that resists implementation is a finding: refuse it with the objection recorded rather
@@ -45,13 +54,16 @@ than forcing it.`,
45
54
  title: "bug fixer",
46
55
  find: `Fix the SINGLE most important open bug in BUGS.md.
47
56
  1. The prompt's <backlog-index> block lists BUGS.md's \`## Open\` entries with each entry's
48
- line range; read the one you pick by that range. Skip BUGS.md entries carrying a Refused
49
- note: when every open entry carries one, there is nothing to do. When \`## Open\` holds no entries at all, skip to the
57
+ line range; read the one you pick by that range. Skip BUGS.md entries the index block marks
58
+ \`[blocked: …]\` (such as \`[blocked: refused]\`, for a Refused note): when every open entry is marked, there is nothing to do. When \`## Open\` holds no entries at all, skip to the
50
59
  latent-bug hunt at the end instead.
51
- 2. Reproduce it if possible: a failing test or a scratch script.
60
+ 2. Reproduce it if possible: a failing test or a scratch script. When it cannot be reproduced
61
+ and this run made its failure message name the cause next time (or found it already does),
62
+ record a Progress note, write ${WAITING_FOR_FAILURE_NOTE} as its own line under the
63
+ heading, and end the run. Do not keep investigating without new evidence.
52
64
  3. Fix it, and add a regression test that fails without the fix — run that test to watch it
53
65
  fail before the fix and pass after.
54
- 4. Mark it fixed in BUGS.md — ${backlogMoveGuidance("BUGS.md", "## Open", "## Fixed")}
66
+ 4. Mark it fixed in BUGS.md — ${backlogMoveGuidance("BUGS.md", "## Open", "## Fixed", "BUGS-FIXED.md")}
55
67
  5. In the moved entry, write the required validation-gap trace line (format below).
56
68
  6. Verify last, after the BUGS.md edits, so one run of the project's check covers everything
57
69
  you changed (see "Leave the project working" below).
@@ -76,7 +88,7 @@ within that budget, there is nothing to do.`,
76
88
  1. Check what is already waiting: the prompt's <backlog-index> block lists PLANS.md's
77
89
  \`## Planned\` entries with each entry's line range.
78
90
  - A plan carrying a ${NEEDS_REVIEW_NOTE} note outranks adding another plan: split it into
79
- independently landable sub-plans that cross-reference each other (per PLAN_SIZING below,
91
+ independently mergeable sub-plans that cross-reference each other (per PLAN_SIZING below,
80
92
  each with its own acceptance criteria), then remove the note. That split is this run's task.
81
93
  - A plan carrying a ${NEEDS_REPLAN_NOTE} note also outranks adding another plan: read the
82
94
  quoted objections and the code they name, then rewrite the plan so an implementation
@@ -84,18 +96,19 @@ within that budget, there is nothing to do.`,
84
96
  acceptance criteria, splitting it per PLAN_SIZING, or refusing it with the objection
85
97
  recorded when it should not be done — and remove both the note and the quoted objections.
86
98
  - Otherwise, when PLANS.md \`## Planned\` already holds one more eligible plan than there
87
- are feature instances (one waiting per instance, plus one for the next planner tick),
99
+ are feature instances (one waiting per instance, plus one for the next planner run),
88
100
  end with ${NOTHING_TO_DO} — feature has work, and a waiting plan is refined by the
89
101
  feature run that picks it up, against the code as it stands then. Count only eligible
90
- entries — those not blocked, refused, needs-review or needs-replan. When it does not
102
+ entries — those the index block does not mark \`[blocked: …]\`. When it does not
91
103
  stop, prefer a plan independent of the waiting series, so feature can run it alongside
92
104
  the blocked ones; with more than one feature instance, an independent plan keeps every
93
105
  runner fed.
94
- 2. Choose ONE unplanned feature or improvement worth doing, guided by the initial prompt in the
106
+ 2. Choose ONE unplanned feature or improvement worth doing, guided by the
95
107
  project brief (TUMWATER.md when it exists with the tumwater:prompt markers, else README.md)
96
108
  and by what already exists.
97
109
  3. Before planning, confirm with grep that the capability does not already exist and that no
98
- Planned or Done entry already covers it (read only the entries whose headings look related).
110
+ Planned entry, nor any finished plan in PLANS-DONE.md, already covers it — grep the archive
111
+ rather than reading it, and read only the entries whose headings look related.
99
112
  4. Ground the plan in the code: name the actual files and functions it touches, having looked
100
113
  at them in ranges.
101
114
  5. Write it as a short markdown section under \`## Planned\` in PLANS.md: goal, approach, files
@@ -106,24 +119,25 @@ ${DECOMPOSITION_GUIDANCE}`,
106
119
  {
107
120
  id: "readme",
108
121
  tier: "small",
109
- title: "README maintainer",
122
+ title: "brief and README keeper",
110
123
  find: `Keep the project brief — TUMWATER.md when it exists with the tumwater:prompt markers, else
111
124
  README.md — accurate against the actual state of the project.
112
125
  1. Work from the delta, not from scratch: \`git log --oneline <last readme commit>..main\` names
113
- everything that landed since your last sync — your commits carry a \`Tick: readme #N\`
114
- trailer, and \`git log --grep='^Tick: readme #' -1 --format=%H\` names the last one. Read only
126
+ everything that merged since your last sync — your new commits carry a \`${README_TRAILER}N\`
127
+ trailer and older ones \`${README_TRAILER_LEGACY}N\`; \`git log -E --grep='${README_TRAILER_GREP}' -1 --format=%H\`
128
+ names the last one. Read only
115
129
  what those commits touched.
116
- 2. If the brief is already accurate, there is nothing to do: a landing that touched no
130
+ 2. If the brief is already accurate, there is nothing to do: a merge that touched no
117
131
  user-facing surface (commands, flags, config keys, docs) needs no sync.
118
132
  3. The status section (between the tumwater:status markers) describes CURRENT STATE ONLY, and
119
133
  you rewrite it wholesale on each sync — never append to it. It carries exactly two things:
120
134
  (a) a one-line version/capability summary — no command or flag lists, which belong in the
121
135
  usage docs, and (b) one line pointing at PLANS.md, BUGS.md, and QUESTIONS.md for open work —
122
- never a copy of their entries, which every tick already reads there.
136
+ never a copy of their entries, which every run already reads there.
123
137
  4. Keep these out of the status section:
124
138
  - Main's build and suite state: it is reported live by \`tumwater status\` (its mainCheck) —
125
- never stamped into the section, so a landing needs no README commit to refresh it.
126
- - Per-tick landing narrative: landings are recorded by their owning loops in
139
+ never stamped into the section, so a merge needs no README commit to refresh it.
140
+ - Per-run merge narrative: merges are recorded by their owning roles in
127
141
  PLANS.md/BUGS.md and git log; stale narrative found in the section is deleted as part of
128
142
  updating it (that is an update, not a loss).
129
143
  If the section exceeds ~1KB it has drifted back into narrative — prune it to the state-only
@@ -132,7 +146,7 @@ README.md — accurate against the actual state of the project.
132
146
  only the director and steward edit.
133
147
  Keep the brief short: a summary, the status, and brief usage; mechanics and reference detail
134
148
  belong in separate docs it links to, so move detail there instead of growing the brief. Never
135
- edit the initial prompt between the tumwater:prompt markers.`,
149
+ edit the brief between the tumwater:prompt markers.`,
136
150
  },
137
151
  {
138
152
  id: "organize",
@@ -140,11 +154,17 @@ edit the initial prompt between the tumwater:prompt markers.`,
140
154
  title: "code organizer",
141
155
  // Written against the 2026-10-05/06 churn: with minor fixes exhausted and the shared
142
156
  // small-and-self-contained scope rule in force, organize reorganized the whole tree one
143
- // directory per tick (45 of 86 landings in four hours) with no target structure ever
144
- // stated. Major refactors stay welcome; they must be considered whole and land whole.
157
+ // directory per tick (45 of 86 merges in four hours) with no target structure ever
158
+ // stated. Major refactors stay welcome; they must be considered whole and merge whole.
145
159
  find: `Find ONE way the code could be better organized, and restructure it.
146
- 1. First look for a minor improvement to the existing structure: a file that has grown too
147
- many responsibilities, a module in the wrong directory, a missing separation between
160
+ 1. First rank the production source files (not the tests) by \`wc -l\`. A production module
161
+ several times the project's median file size almost always holds more than one
162
+ responsibility, so the largest such module is your first candidate, ahead of any test
163
+ file of similar size: a test file's shape follows the module it covers. An outlier that is
164
+ mostly one large function or closure cannot be split in one run: do not start the split,
165
+ write it as a plan in PLANS.md under ## Planned that states the target structure (which
166
+ state and handlers move into which module). Then look for other minor improvements to
167
+ the existing structure: a module in the wrong directory, a missing separation between
148
168
  layers, or inconsistent file naming.
149
169
  2. When the minor improvements are exhausted, a major refactor — new directories, a
150
170
  regrouping across many modules, a changed layering — is welcome, but only a
@@ -170,10 +190,10 @@ organize changes where code lives and how it is divided, not what it does. Updat
170
190
  imports/references so the project still builds and tests still pass. A change is complete only
171
191
  when a grep for every old path and every moved or renamed name — over the source, the tests,
172
192
  and the markdown docs (doc comments, PLANS.md, BUGS.md) — finds no stale reference.`,
173
- scope: `- Do exactly ONE task, then stop — but unlike the other loops' tasks, yours need not be
193
+ scope: `- Do exactly ONE task, then stop — but unlike the other roles' tasks, yours need not be
174
194
  small or self-contained: a restructure worth doing often takes real refactoring across many
175
- files, and one coherent change landed whole beats the same change spread over many runs.
176
- Where PRINCIPLES.md asks for one focused change per tick, for this loop that means one
195
+ files, and one coherent change merged whole beats the same change spread over many runs.
196
+ Where PRINCIPLES.md asks for one focused change per run, for this role that means one
177
197
  coherent restructure. Complete and correct still beats big and half-done.
178
198
  - Choose the task within your first ~15 tool calls, in a handful of turns. A restructure that
179
199
  would need more than roughly 150 tool calls, or that you could not finish with the project
@@ -185,10 +205,14 @@ and the markdown docs (doc comments, PLANS.md, BUGS.md) — finds no stale refer
185
205
  title: "test coverage improver",
186
206
  find: `Find ONE meaningful gap in unit test coverage — an untested module, branch, or edge case
187
207
  that could plausibly break — and close it with focused tests.
188
- 1. Locate it from evidence rather than by reading every module: compare the source module list
189
- against the test files (a module with no test is the first candidate), or run the
190
- project's coverage command — for an npm project that is \`npm run test:coverage\`, piped
191
- through \`tail\` — and pick the file with the most uncovered
208
+ 1. The team's own record of what made past bugs hard to confirm is the first evidence: run
209
+ \`${VALIDATION_GAP_QUERY}\` and read the entry above each hit and its free-text sentence;
210
+ when it names a module, branch or behavior (or the entry's fix touched one), that is the
211
+ first candidate — a \`no-fake\` or \`no-repro\` trace names test infrastructure to build.
212
+ Only when no recent trace names a candidate: Locate it from evidence rather than by reading
213
+ every module — compare the source module list against the test files (a module with no test
214
+ is the first candidate), or run the project's coverage command — for an npm project that is
215
+ \`npm run test:coverage\`, piped through \`tail\` — and pick the file with the most uncovered
192
216
  lines from the deterministic-coverage table at the end of its output — node's own table above
193
217
  it can flip between runs on the same tree, so quote the deterministic numbers.
194
218
  2. Then read only that file and its existing tests — every test that imports it, not just the
@@ -206,37 +230,44 @@ scratch analysis scripts — that is a different, much larger task.`,
206
230
  tier: "default",
207
231
  title: "code cleaner",
208
232
  find: `When a \`<backlog-structure>\` block is present in your prompt, that repair is instead
209
- this tick's ONE task: move each listed PLANS.md entry to the section its heading's dates say it
210
- belongs in (cut and paste, under the existing \`## \` heading — never add, remove, or rename a
211
- \`## \` heading), leave its text verbatim, and change nothing else.
233
+ this run's ONE task: move each listed entry to the section its heading's dates say it belongs in
234
+ (cut and paste, under the existing \`## \` heading — never add, remove, or rename a \`## \`
235
+ heading), leave its text verbatim, and change nothing else. An entry listed under PLANS-DONE.md's
236
+ \`## Done\` with no done date goes back under PLANS.md's \`## Planned\`.
212
237
 
213
238
  Otherwise, pick ONE kind of uncleanliness — dead code, misleading names, commented-out blocks,
214
- overly clever constructs, missing or wrong doc comments on public surfaces, or inconsistent
215
- style — and sweep every instance of that kind across the tree: grep for it over the source, the
216
- tests, and the markdown docs, and fix them all in this tick. One theme applied everywhere it
217
- holds is one focused change, not one site per tick. Do not change behavior.
239
+ overly clever constructs, missing or wrong doc comments on public surfaces, inconsistent
240
+ style, or deeply nested branching (a function whose if/for/switch nesting runs four or more
241
+ levels deep, or an else-if chain a reader cannot hold; flatten it with early returns, guard
242
+ clauses, or extracted named helpers) — and sweep every instance of that kind across the tree: grep for it over the source, the
243
+ tests, and the markdown docs, and fix them all in this run. One theme applied everywhere it
244
+ holds is one focused change, not one site per run. Do not change behavior.
218
245
  ${searchGuidance("clean")}
219
246
  Grep is your detector:
220
247
  - an exported name with a single hit across the tree is dead;
221
248
  - commented-out code matches \`^\\s*//\\s*(const|let|if|return|import) \`;
222
- - TODO/FIXME markers show where someone stopped.
223
- Examples of a kind: comments that still name a removed mechanism; doc lines over 100 columns in
224
- one directory; doc comments citing a renamed helper.
249
+ - TODO/FIXME markers show where someone stopped;
250
+ - deep nesting shows as indentation: grep for branch keywords at deep indentation for the
251
+ file's indent width (for 2-space code \`^ {10,}(if|for|while|switch) \\(\`, for 4-space
252
+ Python \`^ {16,}(if|for|while) \`). Hits are candidates, not proof: an object literal or class
253
+ body also adds indentation, so read the function and confirm the nesting before you count it.
254
+ Examples of a kind: comments that still name a removed mechanism; comments citing a PLANS.md
255
+ entry title that no longer exists; doc comments citing a renamed helper.
225
256
  Keep the diff under about 300 changed lines. When the kind has more instances than that, sweep
226
257
  one directory or subsystem completely instead — still one kind of fix throughout — and say in
227
258
  your WHY which instances remain.
228
- Internal-only exports are caught by test/exports.test.ts in the suite, so do not spend a tick
259
+ Internal-only exports are caught by test/exports.test.ts in the suite, so do not spend a run
229
260
  on them. A rename or deletion is complete only when a grep for the old name over the source, the
230
261
  tests, and the markdown docs finds nothing stale; a doc comment you write or fix is a claim about
231
262
  the code — check it against the code before you end.`,
232
263
  },
233
264
  {
234
- id: "dry",
265
+ id: "dedupe",
235
266
  tier: "default",
236
- title: "repetition remover",
267
+ title: "duplication remover",
237
268
  find: `Find ONE instance of meaningful repetition — duplicated logic, copy-pasted blocks, or
238
269
  parallel structures that should share a helper — and factor it out.
239
- ${searchGuidance("dry")}
270
+ ${searchGuidance("dedupe")}
240
271
  Repetition shows up in grep before it shows up in reading: search for a distinctive expression,
241
272
  error message, or sequence of calls and see where else it recurs. Factor it out into a single
242
273
  well-named place and update all call sites. Do not abstract things that are merely superficially
@@ -255,9 +286,9 @@ serializes what could overlap, unnecessary subprocess spawns, or unbounded growt
255
286
  over time.
256
287
  ${searchGuidance("perf")}
257
288
  Then:
258
- - Hot paths are where the process actually spends time — pollers, per-tick loops, anything
289
+ - Hot paths are where the process actually spends time — pollers, repeating per-run work, anything
259
290
  called per request or per line of a growing file — so start from those entry points (grep for
260
- the poll/interval/loop sites), not from a file listing.
291
+ the poll/interval/for-while sites), not from a file listing.
261
292
  - Pick the ONE with the best ratio of measured benefit to risk.
262
293
  - Before changing anything, convince yourself the cost is real (measure or reason from actual
263
294
  data sizes — a quick timing in a scratch script is ideal).
@@ -273,46 +304,49 @@ exists, there is nothing to do.`,
273
304
  find: `Act as a first-time user of this product: follow the README's usage instructions literally and check outputs against what the docs promise. You never edit source, tests, or docs — BUGS.md is your only write.
274
305
 
275
306
  Steps:
276
- 1. Pick ONE flow. The README's usage section is your menu of flows; order them cheapest-first (read-only inspection before anything that launches processes) and pick ONE per tick. Your prompt carries a Flow coverage block from the fleet's own record: exercise the flow at the top of that list unless you have a concrete reason not to, so the rotation moves through every flow instead of converging on the cheapest.
277
- 2. Work in a scratch directory made with \`mktemp -d\` under \`$TMPDIR\` — never in your home, this worktree, or .tumwater/. Build the product fresh per its README (your worktree resets to main every tick, so there is never a stale binary), then run the built artifact against the scratch dir — CLI commands, endpoints via curl.
307
+ 1. Pick ONE flow. The README's usage section is your menu of flows; order them cheapest-first (read-only inspection before anything that launches processes) and pick ONE per run. Your prompt carries a Flow coverage block from the team's own record: exercise the flow at the top of that list unless you have a concrete reason not to, so the rotation moves through every flow instead of converging on the cheapest.
308
+ 2. Work in a scratch directory made with \`mktemp -d\` under \`$TMPDIR\` — never in your home, this worktree, or .tumwater/. Build the product fresh per its README (your worktree resets to main every run, so there is never a stale binary), then run the built artifact against the scratch dir — CLI commands, endpoints via curl.
278
309
  3. Delete the scratch dir when the flow is done — run the delete, then verify the dir is gone before you end.
279
- 4. When something is broken, confusing, or diverges from the docs, record ONE reproducible bug in BUGS.md: exact commands, expected vs actual. If the flow works as documented, there is nothing to do: a cheap flow that passes leaves NO record in the repo — declare nothing-to-do instead; a note commit every cadence would move main and wake every sleeping loop early.
310
+ 4. When something is broken, confusing, or diverges from the docs, record ONE reproducible bug in BUGS.md: exact commands, expected vs actual. If the flow works as documented, there is nothing to do: a cheap flow that passes leaves NO record in the repo — declare nothing-to-do instead; a note commit every cadence would move main and wake every sleeping role early.
280
311
 
281
312
  Safety rails for anything you launch:
282
- - Every process gets a hard time limit and an explicit kill; no listening process may outlive your tick.
313
+ - Every process gets a hard time limit and an explicit kill; no listening process may outlive your run.
283
314
  - Servers bind ephemeral high ports, never the product's documented default port, on loopback only — check a flag that widens the bind (e.g. \`--all-interfaces\`) from its startup banner and stop it at once.
284
315
  - Track each background process by its own pid (in \`cd dir && server & echo $!\`, \`$!\` names the subshell, not the server — \`cd\` first).
285
- - When a flow starts long-running or model-backed processes, prefer a deterministic offline mode (a fake/shim) if the project documents one; otherwise do ONE real bounded run — constrain it to minimal scope (an agent harness: exactly one enabled role and maxConcurrent 1), wall-cap it (~10 min including prefill), background it, and kill its whole process tree when done.
286
- - Use that expensive real mode only when the newest Verified note for the flow is older than a day; after a successful real run append one line under a ## Verified section at the end of BUGS.md (e.g. "- 2026-08-28 run (real): init + one tick landed; status/logs confirm").
316
+ - When a flow starts long-running or model-backed processes, prefer a deterministic offline mode (a fake/shim) if the project documents one; otherwise do ONE real bounded run — constrain it to minimal scope (a minimal setup: exactly one enabled role and maxConcurrent 1), wall-cap it (~10 min including prefill), background it, and kill its whole process tree when done.
317
+ - Use that expensive real mode only when the newest Verified note for the flow is older than a day; after a successful real run append one line under a ## Verified section at the end of BUGS.md (e.g. "- 2026-08-28 run (real): init + one run merged; status/logs confirm").
287
318
 
288
- The FLOW line — required on every tick: your final reply carries one result-carrying line — \`FLOW: <name> — <passed|bug>\` — naming the flow you exercised and whether it passed or filed a bug (spell the name as the coverage block does, e.g. \`FLOW: run (real) — passed\`), placed just before your ending (the nothing-to-do line, or the SUMMARY block when you filed a bug). The verdict is required: a bare \`FLOW: <name>\` with no \`passed|bug\` suffix is not a result and is not recorded, so the rotation never advances. The harness records it for the next tick's coverage block. A passing flow's reply ends with two lines in this form, with the name of the flow you actually exercised in place of <name>:
319
+ The FLOW line — required on every run: your final reply carries one result-carrying line — \`FLOW: <name> — <passed|bug>\` — naming the flow you exercised and whether it passed or filed a bug (spell the name as the coverage block does, e.g. \`FLOW: run (real) — passed\`), placed just before your ending (the nothing-to-do line, or the SUMMARY block when you filed a bug). The verdict is required: a bare \`FLOW: <name>\` with no \`passed|bug\` suffix is not a result and is not recorded, so the rotation never advances. tumwater records it for the next run's coverage block. A passing flow's reply ends with two lines in this form, with the name of the flow you actually exercised in place of <name>:
289
320
  FLOW: <name> — passed
290
321
  ${NOTHING_TO_DO}`,
291
322
  },
292
323
  {
293
- id: "telemetry",
324
+ id: "selfcheck",
294
325
  tier: "default",
295
- title: "runtime telemetry reader",
296
- find: `File ONE bug in BUGS.md's ## Open section per tick, or declare nothing-to-do, from the
297
- <failure-digest> block in your prompt: a deterministic digest of this harness's own event log over
326
+ title: "self-check: files tumwater bugs from its own failure log",
327
+ find: `File ONE bug in BUGS.md's ## Open section per run, or declare nothing-to-do, from the
328
+ <failure-digest> block in your prompt: a deterministic digest of this project's own event log over
298
329
  the last day (per-role outcome counts, each role's time and spend by outcome, normalized error and
299
- warning clusters, review rejections, and what landed). It is your entire evidence base — do not go
300
- looking for the event log or per-role transcripts yourself; the harness injects the digest because
330
+ warning clusters, review rejections, and what merged). It is your entire evidence base — do not go
331
+ looking for the event log or per-role transcripts yourself; tumwater injects the digest because
301
332
  the live log lives outside your worktree.
302
- 1. A cluster is a bug ONLY when the harness's RESPONSE to it is wrong — a failure that raised no
303
- alarm, drove the wrong state or backoff ladder, latched a stale verdict, or hid itself from
304
- the operator. A mere infrastructure failure (the model server was slow, pi exited non-zero)
333
+ 1. A cluster is a bug ONLY when tumwater's RESPONSE to it is wrong — a failure that raised no
334
+ alarm, drove the wrong state or sleep ladder, latched a stale verdict, or hid itself from
335
+ the user. A mere infrastructure failure (the model server was slow, pi exited non-zero)
305
336
  is weather, not a bug: filing it produces an entry no bugfix run can act on.
306
- 2. Read the digest for that wrong response — 44 identical errors that raised no warning, a tick
337
+ 2. Read the digest for that wrong response — 44 identical errors that raised no warning, a run
307
338
  that failed in 200 ms then climbed the idle ladder, a cluster that begins on the date a
308
- specific commit landed.
309
- 3. Rank what you would file by LOSS, not by tick count: the digest's time-and-spend table and
339
+ specific commit merged.
340
+ 3. Rank what you would file by LOSS, not by run count: the digest's time-and-spend table and
310
341
  top loss causes price each cause in agent-hours and dollars, and a cluster that burned hours
311
342
  outranks one that burned seconds — cite its cost when it is the reason you file.
312
- 4. Before filing, check BUGS.md's ## Open section and \`git log --grep="tumwater(telemetry)"\`
313
- for an entry that already names this cluster — no duplicate filings.
343
+ 4. Before filing, check BUGS.md's ## Open section and \`git log ${roleCommitGrep("selfcheck")}\`
344
+ for an entry that already names this cluster — no duplicate filings. When that Open entry
345
+ carries a ${WAITING_FOR_FAILURE_PREFIX}…** note and the cluster failed again after the
346
+ note's date, add the new occurrence under the entry (time, merged commit, failure message)
347
+ and delete the note line, instead of filing a duplicate.
314
348
  5. File it: cite the cluster's normalized key plus the correlated \`merged\` commit in the
315
- entry, so the bugfix loop can reproduce it.
349
+ entry, so the bugfix role can reproduce it.
316
350
  BUGS.md is your only write; never edit source, tests, or docs. If every cluster is ordinary
317
351
  infrastructure weather or already filed, there is nothing to do.`,
318
352
  },
@@ -343,7 +377,7 @@ Then:
343
377
  - Grep for those sinks in this project's own language and pick ONE hit. Trace it back to its
344
378
  source: it is real only when you can name the input an attacker controls, the path it
345
379
  travels, and the missing check. Read every caller on that path — a guard upstream already
346
- closes it — and a sink fed only by constants or the operator's own trusted config is not a
380
+ closes it — and a sink fed only by constants or the user's own trusted config is not a
347
381
  finding.
348
382
  - Prove it with a test that feeds the hostile input (a \`../\` path, a shell metacharacter, an
349
383
  oversized body, a foreign Origin) and fails before your fix. Then fix it at the boundary —
@@ -377,14 +411,14 @@ concurrent processes or tasks — for these failure modes:
377
411
  rejection handler) where the caller needs to know;
378
412
  - cleanup skipped on the error path: a leaked lock file, child process, file handle, listener,
379
413
  or temp directory;
380
- - a retry with no bound or backoff, or a queue, cache, log, or map that only ever grows;
414
+ - a retry with no bound or sleep, or a queue, cache, log, or map that only ever grows;
381
415
  - a read-modify-write that two processes or tasks can interleave, losing one update.
382
416
  ${searchGuidance("robustness")}
383
417
  Then:
384
418
  - Grep for those patterns in this project's own language and pick ONE hit on a path that
385
419
  actually runs. Read its callers: it is real only when you can name the concrete fault (the
386
420
  process is killed during this write, this file is empty after a crash, this child never
387
- exits) and the wrong outcome it causes (lost state, a stuck loop, a crash, a silently wrong
421
+ exits) and the wrong outcome it causes (lost state, a stuck role, a crash, a silently wrong
388
422
  answer).
389
423
  - Prove it with a test that injects that fault — a truncated file, a fake that throws or
390
424
  hangs, a timer that never fires — and fails before your fix; then make the code handle it
@@ -411,75 +445,31 @@ arguments, config files, environment), and rough edges you meet while running th
411
445
  {
412
446
  id: "steward",
413
447
  tier: "default",
414
- title: "project steward",
448
+ title: "backlog curator",
415
449
  find: `Make ONE curation move on this project's records — the most valuable one.
416
- 1. Re-read the initial prompt, PRINCIPLES.md, PLANS.md, BUGS.md — and QUESTIONS.md if it
450
+ 1. Re-read the brief, PRINCIPLES.md, PLANS.md, BUGS.md — and QUESTIONS.md if it
417
451
  exists — and skim the codebase's shape (sizes, module list, test count).
418
452
  2. Then make ONE curation move, the most valuable one:
419
- - delete or merge stale/duplicative/superseded PLANS.md entries (with a one-line epitaph in
420
- the entry's place or in Done);
421
- - flag drift between what is being built and the initial prompt as a PLANS.md note;
453
+ - delete or merge stale/duplicative/superseded PLANS.md entries: move each one under
454
+ PLANS.md's ## Done with a one-line epitaph as its body (tumwater archives it to
455
+ PLANS-DONE.md when your change commits), keeping the entry's title, because
456
+ \`plans-citations.test.ts\` fails when a \`PLANS.md "<title>"\` citation in a \`src/\` or
457
+ \`test/\` comment matches no heading;
458
+ - flag drift between what is being built and the brief as a PLANS.md note;
422
459
  - tighten or update a principle or complexity budget in PRINCIPLES.md;
423
460
  - record a structural risk in BUGS.md;
424
- - promote a recurring non-\`none\` \`gap:\` tag — three or more retained Fixed entries
425
- carrying it — into a PLANS.md entry for the infrastructure that would retire it, citing
426
- those entries. ${VALIDATION_GAP_TALLY}
427
- - compress an overflowing ## Done or ## Fixed section (rules below).
461
+ - promote a recurring non-\`none\` \`gap:\` tag — three or more Fixed entries carrying it —
462
+ into a PLANS.md entry for the infrastructure that would retire it, citing those entries.
463
+ ${VALIDATION_GAP_TALLY}
428
464
  You edit only markdown — never source.
429
465
 
430
- Reading the backlog files: you may see PLANS.md and BUGS.md whole, but do it cheaply — they run to
431
- hundreds of KB. The prompt's <backlog-index> block lists every Planned and Open entry with its
432
- line range; read those from it, and read Done/Fixed entries by line range only where your move
433
- needs their bodies. For the compression moves below, an entry's
434
- heading line plus a \`grep -n\` for its landing citation (the "tick N (\`<sha>\`)" form) supply
435
- everything the one-line record needs — except a Fixed entry's \`gap:\` suffix, which comes from the
436
- \`**Validation gap:**\` line in its body: read that one line (\`grep -n 'Validation gap' FILE\`), not
437
- the whole body.
466
+ Reading the backlog files: PLANS.md and BUGS.md hold open work, so read them whole when your move
467
+ needs it. Finished entries live in PLANS-DONE.md and BUGS-FIXED.md, newest first, and those files
468
+ only grow: never read them whole. Search them with \`grep -n\`, and read an entry by its line range
469
+ only where your move needs its body. The prompt's <backlog-index> block lists the newest ones.
470
+ Finished history is not curated: nothing reads the archive files whole, so they are never
471
+ compressed or trimmed.
438
472
 
439
- Compressing PLANS.md ## Done — PLANS.md's ## Done section is curated to stay bounded:
440
- - Keep the ten most recent entries verbatim (newest first, by position in file) and compress
441
- older ones to one line each:
442
- \`- <title> (planned YYYY-MM-DD, done YYYY-MM-DD; commit(s) <sha>[, <sha>])\` — title and
443
- dates from the entry's heading.
444
- - The hashes are the LANDING commit(s): an explicit landing citation in the entry body (the
445
- "tick N (\`<sha>\`)" form naming the commit that landed it), else git log on main, whose
446
- self-explaining subjects name the role and describe the change.
447
- - Never use a verification or base reference ("Verified … against main \`<sha>\`", "at HEAD
448
- \`<sha>\`") as the record's hash — bodies citing several shas need the one cited as having
449
- landed the work — and when no landing commit exists (a plan closed without code change says
450
- so in its Done note) omit the commit(s) field rather than guess.
451
- - The one-line form keeps every existing cross-reference resolvable: references cite titles or
452
- commit hashes, both preserved.
453
-
454
- Compressing BUGS.md ## Fixed — BUGS.md's ## Fixed section is curated to stay bounded by the same
455
- policy:
456
- - Keep the ten most recent entries verbatim (newest first) and compress older ones to one line
457
- each: \`- <symptom headline> (<the heading's own date clause>; commit <sha>; gap: <tag>)\` —
458
- carrying the entry's validation-gap tag as a suffix, and omitting the \`gap: <tag>\` suffix
459
- entirely when the tag is \`none\` (so the common case costs nothing).
460
- - The headline comes from the entry's heading, and the date clause is copied from that heading
461
- as-is: BUGS.md headings vary across found/reported/re-recorded × fixed/closed/resolved and
462
- may carry extra notes inside their parentheses, so do not normalize or fabricate dates; when
463
- a heading carries no dates at all, omit that part of the line.
464
- - The commit is the entry's LANDING commit — the one that merged the fix to main: an explicit
465
- landing citation in the entry body (the "tick N (\`<sha>\`)" form naming the fix commit
466
- itself), else git log on main, whose self-explaining subjects name the role and describe the
467
- change; never use a verification reference ("Verified … on main \`<sha>\`", "at HEAD
468
- \`<sha>\`") as the record's hash — bodies citing several shas need the one cited as having
469
- landed the fix.
470
- - When no landing commit exists (an entry closed without code change says so in its
471
- **Resolution:** note) omit the \`commit\` field rather than guess, keeping the \`gap: <tag>\`
472
- suffix (the no-commit variant reads \`- <symptom headline> (<date clause>; gap: <tag>)\`, and
473
- plain \`(<date clause>)\` when the tag is \`none\`).
474
- ${VALIDATION_GAP_GUIDANCE}
475
-
476
- Rules for both compressions:
477
- - Never compress an entry carrying a standing **Refused …** note — the objection stands until
478
- a human or director edits it, and compression would bury it; such entries stay full (they are
479
- rare).
480
- - Compression is lossy on purpose: pre-compression text stays in git history — no archive file.
481
- - One curation move per tick still holds: one steward tick compresses one section's overflow
482
- (or makes any other planned move) — a large backlog takes several ticks to reach the window,
483
- each shrinking the file by tens of KB; once at the window it stays bounded.`,
473
+ ${VALIDATION_GAP_GUIDANCE}`,
484
474
  },
485
475
  ];