tumwater 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +55 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +6 -4
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +404 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +65 -3
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +181 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +183 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,7 +1,7 @@
1
- // vocabulary: plain
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
- import { readTextOrNull } from "../files/files.js";
3
+ import { errCode } from "../errno.js";
4
+ import { isJsonObject } from "../files/json-object.js";
5
5
  import { readJsonFile } from "../files/json-files.js";
6
6
  import { changeBaseRev, fileContentAt } from "../git/git.js";
7
7
  import { piModelsPath } from "../pi/pi-models.js";
@@ -32,9 +32,6 @@ export function secretEnvValues(env) {
32
32
  }
33
33
  return out;
34
34
  }
35
- function isRecord(v) {
36
- return typeof v === "object" && v !== null && !Array.isArray(v);
37
- }
38
35
  /** The credentials pi stores: literal `apiKey`s and header values in `models.json`, and the
39
36
  * `key`/`access`/`refresh` fields (plus secret-named `env` values) of the sibling `auth.json`.
40
37
  * `$NAME` interpolations (secretEnvValues covers those) and `!command` values are skipped. A
@@ -48,12 +45,12 @@ export function piCredentialValues(modelsPath = piModelsPath()) {
48
45
  out.push({ name, value });
49
46
  };
50
47
  const models = readJsonFile(modelsPath);
51
- if (models && isRecord(models.providers)) {
48
+ if (models && isJsonObject(models.providers)) {
52
49
  for (const [p, cfg] of Object.entries(models.providers)) {
53
- if (!isRecord(cfg))
50
+ if (!isJsonObject(cfg))
54
51
  continue;
55
52
  add(`models.json ${p}.apiKey`, cfg.apiKey);
56
- if (isRecord(cfg.headers)) {
53
+ if (isJsonObject(cfg.headers)) {
57
54
  for (const [h, v] of Object.entries(cfg.headers))
58
55
  add(`models.json ${p}.headers.${h}`, v);
59
56
  }
@@ -62,11 +59,11 @@ export function piCredentialValues(modelsPath = piModelsPath()) {
62
59
  const auth = readJsonFile(path.join(path.dirname(modelsPath), "auth.json"));
63
60
  if (auth) {
64
61
  for (const [p, cred] of Object.entries(auth)) {
65
- if (!isRecord(cred))
62
+ if (!isJsonObject(cred))
66
63
  continue;
67
64
  for (const f of ["key", "access", "refresh"])
68
65
  add(`auth.json ${p}.${f}`, cred[f]);
69
- if (isRecord(cred.env)) {
66
+ if (isJsonObject(cred.env)) {
70
67
  for (const [n, v] of Object.entries(cred.env)) {
71
68
  if (SECRET_NAME.test(n))
72
69
  add(`auth.json ${p}.env.${n}`, v);
@@ -85,12 +82,21 @@ export function knownSecrets(env = process.env, modelsPath = piModelsPath()) {
85
82
  ];
86
83
  return all.filter((s, i) => all.findIndex((o) => o.value === s.value) === i);
87
84
  }
85
+ /** Whether `err` says the path holds no file (deleted, or a parent replaced). */
86
+ function noFile(err) {
87
+ const code = errCode(err);
88
+ return code === "ENOENT" || code === "ENOTDIR";
89
+ }
90
+ /** The finding for a changed file the gate could not read. */
91
+ function unreadable(file, err) {
92
+ return `\`${file}\` could not be read to check for leaked secrets (${errCode(err) ?? "error"})`;
93
+ }
88
94
  /** The deterministic gate/stage finding for a change that writes the value of a secret
89
95
  * environment variable or stored pi credential into a file (a Node diagnostic report dumps
90
96
  * the whole environment). A value already present in the file at the merge-base is not this
91
97
  * change's leak. The reason
92
- * names files and variables, never the value. undefined when nothing leaked; never throws on
93
- * an unreadable file. */
98
+ * names files and variables, never the value. undefined when nothing leaked. A changed file
99
+ * that exists but cannot be read counts as a finding; a deleted file does not. */
94
100
  export async function secretLeakReason(wt, mainBranch, files, secrets = knownSecrets()) {
95
101
  if (secrets.length === 0)
96
102
  return undefined;
@@ -100,16 +106,30 @@ export async function secretLeakReason(wt, mainBranch, files, secrets = knownSec
100
106
  const full = path.join(wt, file);
101
107
  let stat;
102
108
  try {
103
- stat = fs.statSync(full);
109
+ // lstat, not stat: a symlink in the change set is not followed. The gate scans the
110
+ // content each change writes, and a change that adds a symlink writes no content — its
111
+ // target (which lstat's isFile() leaves unreadable, unlike stat's, which follows it out
112
+ // of the worktree) is not what the change put there. Following it would read an
113
+ // arbitrary host path a malicious repo can point a link at, and would flag that outside
114
+ // file's contents as this change's leak.
115
+ stat = fs.lstatSync(full);
104
116
  }
105
- catch {
117
+ catch (err) {
118
+ if (!noFile(err))
119
+ leaks.push(unreadable(file, err));
106
120
  continue;
107
121
  }
108
122
  if (!stat.isFile() || stat.size > MAX_SCAN_BYTES)
109
123
  continue;
110
- const head = readTextOrNull(full);
111
- if (head === null)
124
+ let head;
125
+ try {
126
+ head = fs.readFileSync(full, "utf8");
127
+ }
128
+ catch (err) {
129
+ if (!noFile(err))
130
+ leaks.push(unreadable(file, err));
112
131
  continue;
132
+ }
113
133
  const hits = secrets.filter((s) => head.includes(s.value));
114
134
  if (hits.length === 0)
115
135
  continue;
@@ -1,4 +1,3 @@
1
- // vocabulary: plain
2
1
  /** Detecting a reviewer run that re-ran the full suite on a tree the gate's pre-check already
3
2
  * verified — pure string logic over the bash command lines a pi run started, no subprocess or
4
3
  * file I/O. The review prompt tells such a reviewer not to (src/prompt/gate-prompts.ts), but on
@@ -1,23 +1,56 @@
1
1
  import fs from "node:fs";
2
+ import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
2
3
  import { deleteRefIfAt, refSha } from "../git/git.js";
3
4
  import { gitTry, runGit } from "../git/git-run.js";
4
5
  import { branchName, mergeRefName, rejectedRefName, reviewSessionDir, roleInboxDir, roleNotesPath, sessionDir, statePath } from "../paths.js";
5
6
  import { errorMessage } from "../text/text.js";
6
7
  import { LEGACY_ROLE_IDS } from "./legacy-role-ids.js";
8
+ /** Rewrite a JSON state file's `role` field to the id its file name now names; a non-JSON file
9
+ * (or one without a `role` field) is left untouched. `role` is the one field that must match
10
+ * the file name — saveRunnerState writes `statePath(root, state.role)` — so a file moved by
11
+ * migration must not keep its old id inside or it would be saved back to the retired file. */
12
+ function rewriteStateRole(file, role) {
13
+ try {
14
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
15
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
16
+ return;
17
+ const rec = parsed;
18
+ if (rec.role === role)
19
+ return;
20
+ rec.role = role;
21
+ // writeJsonAtomic, not a plain writeFileSync like every other production state writer
22
+ // (INV-AF4): the rewritten file is a role's runner state, and a tmp+rename keeps a crash
23
+ // mid-write (or a concurrent reader) from ever seeing a torn file. A migration that dies
24
+ // before the rename leaves the target at its pre-rewrite bytes — retried next startup —
25
+ // never a truncated state file the runner would read as no state.
26
+ writeJsonAtomic(file, rec);
27
+ }
28
+ catch {
29
+ /* not a JSON state file — leave it alone */
30
+ }
31
+ }
7
32
  /** 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. */
33
+ * names"). Returns one line per artifact, sorted into moved, kept (both
34
+ * ids exist) and failed. State files are the one exception to "kept": when both id-named copies
35
+ * exist and the old-id copy carries the newer end-of-run stamp (`lastRunEndedAt`, or the
36
+ * pre-rename `lastTickEndedAt` a frozen copy carries), that copy is folded over the stale
37
+ * new-name one under the new name (the earlier migration renamed only the file list, not the
38
+ * `role` field inside, so the runner kept writing the old-id name and the two copies drifted —
39
+ * BUGS.md 2026-10-23); otherwise both are left in place. Nothing other than a state file that
40
+ * exists under the new id is overwritten, each artifact fails alone, and pi and native logs stay
41
+ * put as history. */
10
42
  export async function migrateLegacyRoleIds(root, legacy = LEGACY_ROLE_IDS) {
11
- const notes = [];
43
+ const notes = { moved: [], kept: [], failed: [] };
12
44
  for (const [oldId, newId] of Object.entries(legacy)) {
13
45
  const attempt = async (what, move) => {
14
46
  try {
15
47
  const note = await move();
16
- if (note !== null)
17
- notes.push(note);
48
+ if (note === null)
49
+ return;
50
+ (note.startsWith("kept ") ? notes.kept : notes.moved).push(note);
18
51
  }
19
52
  catch (err) {
20
- notes.push(`could not move ${what}: ${errorMessage(err)}`);
53
+ notes.failed.push(`could not move ${what}: ${errorMessage(err)}`);
21
54
  }
22
55
  };
23
56
  await attempt("branch", async () => {
@@ -45,7 +78,47 @@ export async function migrateLegacyRoleIds(root, legacy = LEGACY_ROLE_IDS) {
45
78
  return `moved ${from} to ${to}`;
46
79
  });
47
80
  }
48
- for (const pathFor of [statePath, roleNotesPath, roleInboxDir, sessionDir, reviewSessionDir]) {
81
+ // State files get a one-time heal, not "kept": when both id-named copies exist (a state
82
+ // object the earlier migration renamed without rewriting `role`, so the runner kept saving
83
+ // under the old-id name and the two files drifted), fold them into one — when the old-id copy
84
+ // is the newer one, keep it under the new name and drop the stale new-name copy. A live copy
85
+ // carries `lastRunEndedAt`; a frozen copy (never saved since the earlier rename) carries only
86
+ // the pre-rename `lastTickEndedAt`, so the stamp reads either. When either copy is not a state
87
+ // object (unreadable/torn, or a non-object), their stamps tie, or the new-name copy is the
88
+ // newer one, keep both untouched, so the heal never fabricates or destroys a file it does not
89
+ // understand.
90
+ await attempt(`state ${statePath(root, oldId)}`, async () => {
91
+ const from = statePath(root, oldId);
92
+ const to = statePath(root, newId);
93
+ if (!fs.existsSync(from))
94
+ return null;
95
+ if (!fs.existsSync(to)) {
96
+ fs.renameSync(from, to);
97
+ rewriteStateRole(to, newId);
98
+ return `moved ${from} to ${to}`;
99
+ }
100
+ const stamp = (p) => {
101
+ const j = readJsonFile(p);
102
+ if (typeof j?.lastRunEndedAt === "number")
103
+ return j.lastRunEndedAt;
104
+ return typeof j?.lastTickEndedAt === "number" ? j.lastTickEndedAt : -2; // retired-name-ok: legacy state field the frozen copy carries
105
+ };
106
+ const oldStamp = stamp(from);
107
+ const newStamp = stamp(to);
108
+ if (oldStamp < 0 || newStamp < 0 || oldStamp === newStamp)
109
+ return `kept ${from} and ${to}: both exist`;
110
+ if (oldStamp < newStamp)
111
+ return `kept ${from} and ${to}: both exist`;
112
+ // The old-id file holds the newer state (the live runner kept saving under the old name
113
+ // after the earlier migration). Fold it into the new-name path atomically: renameSync
114
+ // replaces the destination and removes the source in one step, so a crash between the two
115
+ // cannot leave the new-name file torn over the still-present old copy (which would read as
116
+ // -2 next time and wedge the heal in "kept").
117
+ fs.renameSync(from, to);
118
+ rewriteStateRole(to, newId);
119
+ return `healed ${from} and ${to}: kept ${from}, newer`;
120
+ });
121
+ for (const pathFor of [roleNotesPath, roleInboxDir, sessionDir, reviewSessionDir]) {
49
122
  const from = pathFor(root, oldId);
50
123
  const to = pathFor(root, newId);
51
124
  await attempt(from, async () => {
@@ -1,11 +1,10 @@
1
- // vocabulary: plain
2
- /** The role catalog: one built-in loop's identity and its role-specific "find something to do"
1
+ /** The role catalog: one built-in role's identity and its role-specific "find something to do"
3
2
  * instruction text. Split out of roles.ts (2026-10-01, organize) so the catalog's embedded
4
3
  * prompt prose lives apart from the derived registries and classification helpers, which stayed
5
4
  * in roles.ts. Pure catalog data — the shared instruction fragments role prompts embed live in
6
5
  * role-guidance.ts (import-only downward, so prompt assembly depends on both without a cycle).
7
6
  * roles.ts re-exports ROLES and Role, so every existing importer keeps its import path. */
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";
7
+ import { backlogMoveGuidance, CONVERGENCE_HOLD_MARKER, DECOMPOSITION_GUIDANCE, HELD_TAG_PREFIX, NEEDS_REPLAN_NOTE, NEEDS_REVIEW_NOTE, PLAN_QUEUE_GUIDANCE, PLAN_SIZING, PRIOR_HELD_PREFIX, searchGuidance, VALIDATION_GAP_GUIDANCE, VALIDATION_GAP_QUERY, VALIDATION_GAP_TALLY, WAITING_FOR_FAILURE_NOTE, WAITING_FOR_FAILURE_PREFIX, } from "./role-guidance.js";
9
8
  import { roleCommitGrep } from "./legacy-role-ids.js";
10
9
  import { NOTHING_TO_DO } from "../verdict/reply-contract.js";
11
10
  /** The trailer the readme role's commits carry and its `git log --grep` reads back; git parses
@@ -14,10 +13,10 @@ import { NOTHING_TO_DO } from "../verdict/reply-contract.js";
14
13
  const README_TRAILER = "Run: readme #";
15
14
  const README_TRAILER_GREP = "^(Run|Tick): readme #"; // vocabulary-ok: legacy name read from old refs or commits
16
15
  const README_TRAILER_LEGACY = "Tick: readme #"; // vocabulary-ok: legacy name read from old refs or commits
17
- /** The opinionated role catalog. Every loop runs one role; a role's `find` text is
18
- * the role-specific "find something to do" half of the tick prompt.
16
+ /** The opinionated role catalog. Every role has one job; a role's `find` text is
17
+ * the role-specific "find something to do" half of the run prompt.
19
18
  *
20
- * Order matters: it is the scheduling priority when loops are otherwise tied
19
+ * Order matters: it is the scheduling priority when roles are otherwise tied
21
20
  * (e.g. the startup burst), so shipping work (feature, bugfix) outranks hygiene. */
22
21
  export const ROLES = [
23
22
  {
@@ -37,12 +36,18 @@ export const ROLES = [
37
36
  4. A plan too large to finish in this run is not split by you: append the note
38
37
  ${NEEDS_REVIEW_NOTE}
39
38
  under its heading, skip it, and implement the next available plan that fits. You may mark
40
- several oversized entries while scanning, but land exactly one plan.
39
+ several oversized entries while scanning, but land exactly one plan. The held tag you write
40
+ — \`${HELD_TAG_PREFIX}<N>)\` on a Needs-review note, \`${HELD_TAG_PREFIX}<H>)\` on a
41
+ Needs-replan note — is ONE past the count already on the entry: read your own or an earlier
42
+ mark's \`${HELD_TAG_PREFIX}N)\` tag, else the \`${PRIOR_HELD_PREFIX}<N>**\` line a lift left
43
+ behind, else start at 1 — never reset it while the same plan ping-pongs, so three holds
44
+ without a merge reach \`${HELD_TAG_PREFIX}3)\`. A plan that merged and left \`## Planned\`
45
+ carries no marker — the convergence backstop starts fresh there.
41
46
  5. Implement it completely: code, tests, and any docs. The reviewer checks your diff against
42
47
  the entry's files-touched list and acceptance criteria, so land everything the entry
43
48
  promises, or update the entry to say what changed and why. When a plan's anchors no longer
44
49
  match the code, correct the entry in the same change instead of refusing.
45
- 6. Mark the plan done in PLANS.md — ${backlogMoveGuidance("PLANS.md", "## Planned", "## Done", "PLANS-DONE.md")}
50
+ 6. Mark the plan done in PLANS.md — ${backlogMoveGuidance("PLANS.md", "## Planned", "## Done", "PLANS-DONE.md", "done")}
46
51
  7. Verify last, after the PLANS.md edit, so one run of the project's check covers everything
47
52
  you changed (see "Leave the project working" below).
48
53
  A plan that resists implementation is a finding: refuse it with the objection recorded rather
@@ -63,7 +68,7 @@ than forcing it.`,
63
68
  heading, and end the run. Do not keep investigating without new evidence.
64
69
  3. Fix it, and add a regression test that fails without the fix — run that test to watch it
65
70
  fail before the fix and pass after.
66
- 4. Mark it fixed in BUGS.md — ${backlogMoveGuidance("BUGS.md", "## Open", "## Fixed", "BUGS-FIXED.md")}
71
+ 4. Mark it fixed in BUGS.md — ${backlogMoveGuidance("BUGS.md", "## Open", "## Fixed", "BUGS-FIXED.md", "fixed")}
67
72
  5. In the moved entry, write the required validation-gap trace line (format below).
68
73
  6. Verify last, after the BUGS.md edits, so one run of the project's check covers everything
69
74
  you changed (see "Leave the project working" below).
@@ -90,11 +95,30 @@ within that budget, there is nothing to do.`,
90
95
  - A plan carrying a ${NEEDS_REVIEW_NOTE} note outranks adding another plan: split it into
91
96
  independently mergeable sub-plans that cross-reference each other (per PLAN_SIZING below,
92
97
  each with its own acceptance criteria), then remove the note. That split is this run's task.
98
+ - A BUGS.md \`## Open\` entry carrying a ${NEEDS_REVIEW_NOTE} or ${NEEDS_REPLAN_NOTE} note
99
+ (the feature or bugfix role parked it as too large for one run) also outranks adding another
100
+ plan: resolve it in the same run by splitting it into independently fixable sub-bugs (each
101
+ its own BUGS.md heading) then removing the note, or by lifting the note when the work does
102
+ not decompose, since no
103
+ other role takes a held bug. That split is this run's task.
104
+ - A plan held by the automatic convergence hold (${CONVERGENCE_HOLD_MARKER}) also outranks
105
+ adding another plan: the text after the dash is the reviewer's surviving objections, so read
106
+ the quoted objections and the code they name and rewrite the plan so an implementation
107
+ following it would answer each — correcting the approach or the anchors, tightening the
108
+ acceptance criteria, or splitting it per PLAN_SIZING — then remove the note.
93
109
  - A plan carrying a ${NEEDS_REPLAN_NOTE} note also outranks adding another plan: read the
94
110
  quoted objections and the code they name, then rewrite the plan so an implementation
95
111
  following it would answer them — correcting the approach or the anchors, tightening the
96
112
  acceptance criteria, splitting it per PLAN_SIZING, or refusing it with the objection
97
113
  recorded when it should not be done — and remove both the note and the quoted objections.
114
+ - When you lift a Needs-review or Needs-replan note (a split or a rewrite) and the change
115
+ has NOT merged, keep the held count instead of letting it reset: remove any existing
116
+ \`**Previously held N**\` line on the surviving parent and every child part the split
117
+ produces, then write \`${PRIOR_HELD_PREFIX}<N>**\` at the lifted note's count on the
118
+ parent and copy it to each child, so exactly one marker survives and heldCount reads the
119
+ lifted count everywhere — a merged parent (one whose plan left \`## Planned\`) leaves no
120
+ marker, and a note with no \`(held N)\` tag (the automatic convergence hold) is lifted
121
+ without writing a \`**Previously held 0**\` marker.
98
122
  - Otherwise, when PLANS.md \`## Planned\` already holds one more eligible plan than there
99
123
  are feature instances (one waiting per instance, plus one for the next planner run),
100
124
  end with ${NOTHING_TO_DO} — feature has work, and a waiting plan is refined by the
@@ -113,6 +137,7 @@ within that budget, there is nothing to do.`,
113
137
  at them in ranges.
114
138
  5. Write it as a short markdown section under \`## Planned\` in PLANS.md: goal, approach, files
115
139
  touched, acceptance criteria.
140
+ ${PLAN_QUEUE_GUIDANCE}
116
141
  ${PLAN_SIZING}
117
142
  ${DECOMPOSITION_GUIDANCE}`,
118
143
  },
@@ -154,7 +179,7 @@ edit the brief between the tumwater:prompt markers.`,
154
179
  title: "code organizer",
155
180
  // Written against the 2026-10-05/06 churn: with minor fixes exhausted and the shared
156
181
  // small-and-self-contained scope rule in force, organize reorganized the whole tree one
157
- // directory per tick (45 of 86 merges in four hours) with no target structure ever
182
+ // directory per run (45 of 86 merges in four hours) with no target structure ever
158
183
  // stated. Major refactors stay welcome; they must be considered whole and merge whole.
159
184
  find: `Find ONE way the code could be better organized, and restructure it.
160
185
  1. First rank the production source files (not the tests) by \`wc -l\`. A production module
@@ -163,7 +188,8 @@ edit the brief between the tumwater:prompt markers.`,
163
188
  file of similar size: a test file's shape follows the module it covers. An outlier that is
164
189
  mostly one large function or closure cannot be split in one run: do not start the split,
165
190
  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
191
+ state and handlers move into which module).
192
+ ${PLAN_QUEUE_GUIDANCE} Then look for other minor improvements to
167
193
  the existing structure: a module in the wrong directory, a missing separation between
168
194
  layers, or inconsistent file naming.
169
195
  2. When the minor improvements are exhausted, a major refactor — new directories, a
@@ -233,7 +259,8 @@ scratch analysis scripts — that is a different, much larger task.`,
233
259
  this run's ONE task: move each listed entry to the section its heading's dates say it belongs in
234
260
  (cut and paste, under the existing \`## \` heading — never add, remove, or rename a \`## \`
235
261
  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\`.
262
+ \`## Done\` with no done date is already finished and archived there: keep it in place and only add the
263
+ missing \`done YYYY-MM-DD\` to its \`### \` heading — never move it back under PLANS.md's \`## Planned\`.
237
264
 
238
265
  Otherwise, pick ONE kind of uncleanliness — dead code, misleading names, commented-out blocks,
239
266
  overly clever constructs, missing or wrong doc comments on public surfaces, inconsistent
@@ -319,6 +346,52 @@ Safety rails for anything you launch:
319
346
  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>:
320
347
  FLOW: <name> — passed
321
348
  ${NOTHING_TO_DO}`,
349
+ },
350
+ {
351
+ id: "ci",
352
+ tier: "default",
353
+ title: "CI parity runner",
354
+ find: `Run the CI workflow's own jobs against main's tip and repair what fails. You close the gap
355
+ between what GitHub CI runs and what the team's own merge gate skips: the workflow's test job runs
356
+ the project's whole test suite and e2e suite, while the merge gate runs only the affected tests —
357
+ so only you run the whole thing where it counts, before a user, a release tag, or a post-merge
358
+ reviewer finds what a green gate missed.
359
+
360
+ Steps:
361
+ 1. Read .github/workflows/ci.yml fresh each run and execute its jobs' own run: steps verbatim
362
+ against main's tip in this worktree, in order — for an npm project like this repo, that is
363
+ \`npm ci\`, then \`npm test\`, then \`npm run test:e2e\`; when the tip is a push-to-main tip,
364
+ also \`npm pack\` (the workflow's package job). The actions/checkout, setup-node and git
365
+ identity are this worktree's own. Reading the workflow means a step added to it is picked
366
+ up without a code change.
367
+ 2. A failing step is the work: reproduce it, fix the source/tests/docs it names, re-run that
368
+ step until green, and commit the fix as the run's one change. A step that fails once and
369
+ passes on the re-run is a flake — file ONE BUGS.md entry naming the test and the flake and
370
+ change nothing else.
371
+ 3. Whatever a failing step's own cause you cannot fix is filed in BUGS.md with the failing
372
+ output quoted.
373
+
374
+ Boundaries:
375
+ - You never edit .github/workflows/* (you read them) and never weaken a test to make a step
376
+ pass.
377
+ - Your product is a verdict and a fix: when everything is green, declare nothing-to-do (here
378
+ \`no_change\` means "checked, green") rather than inventing busywork.
379
+
380
+ The parity block and the CI line — both required on every run: your prompt carries a CI parity
381
+ block from this team's own record naming the main tip and lock hash the last run covered. When it
382
+ says \`current\` (main unmoved since the last green run covered exactly this tip with exactly this
383
+ lock), declare nothing-to-do in ONE model run without running the suite — that is the cheap no-op
384
+ the block exists for. When it is stale (main moved, the lock changed, or the last run failed), run
385
+ the full workflow as the steps above say. Your final reply then carries one result-carrying line —
386
+ \`CI: <sha> — <passed|failed>\` — naming the main tip you covered and whether it passed or failed,
387
+ placed just before your ending (the nothing-to-do line, or the SUMMARY block when you committed a
388
+ fix). The verdict is required and the sha must be a hex id: a bare \`CI: <sha>\` is not a result and
389
+ is not recorded, so the parity block cannot advance on a truncated reply (the same rule as qa's
390
+ FLOW line). tumwater records it for the next run's parity block. A passing run's reply ends with two
391
+ lines in this form:
392
+ CI: <sha> — passed
393
+ ${NOTHING_TO_DO}
394
+ `,
322
395
  },
323
396
  {
324
397
  id: "selfcheck",
@@ -330,6 +403,9 @@ the last day (per-role outcome counts, each role's time and spend by outcome, no
330
403
  warning clusters, review rejections, and what merged). It is your entire evidence base — do not go
331
404
  looking for the event log or per-role transcripts yourself; tumwater injects the digest because
332
405
  the live log lives outside your worktree.
406
+ The digest ends with a ## Fast merge guardrails section when the post-merge machinery is failing
407
+ quietly (main red over 30 minutes, quarantine aged or oversized). Each line there is tumwater's
408
+ own response failing, so it is fileable as it stands under rule 1.
333
409
  1. A cluster is a bug ONLY when tumwater's RESPONSE to it is wrong — a failure that raised no
334
410
  alarm, drove the wrong state or sleep ladder, latched a stale verdict, or hid itself from
335
411
  the user. A mere infrastructure failure (the model server was slow, pi exited non-zero)
@@ -349,6 +425,44 @@ the live log lives outside your worktree.
349
425
  entry, so the bugfix role can reproduce it.
350
426
  BUGS.md is your only write; never edit source, tests, or docs. If every cluster is ordinary
351
427
  infrastructure weather or already filed, there is nothing to do.`,
428
+ },
429
+ {
430
+ id: "feedback",
431
+ tier: "default",
432
+ title: "feedback: files tumwater bugs upstream on GitHub",
433
+ find: `File ONE bug per run as a GitHub issue on github.com/znation/tumwater, or declare
434
+ nothing-to-do, from the <failure-digest> block in your prompt: a deterministic digest of this
435
+ project's own event log over the last day (per-role outcome counts, each role's time and spend by
436
+ outcome, normalized error and warning clusters, review rejections, and what merged). It is your
437
+ entire evidence base — do not go looking for the event log or per-role transcripts yourself; tumwater
438
+ injects the digest because the live log lives outside your worktree.
439
+ The digest ends with a ## Fast merge guardrails section when the post-merge machinery is failing
440
+ quietly (main red over 30 minutes, quarantine aged or oversized). Each line there is tumwater's
441
+ own response failing, so it is fileable as it stands under rule 1.
442
+ 1. A cluster is a bug ONLY when tumwater's RESPONSE to it is wrong — a failure that raised no
443
+ alarm, drove the wrong state or sleep ladder, latched a stale verdict, or hid itself from
444
+ the user. A mere infrastructure failure (the model server was slow, pi exited non-zero)
445
+ is weather, not a bug: filing it produces an issue no maintainer can act on.
446
+ 2. Read the digest for that wrong response — 44 identical errors that raised no warning, a run
447
+ that failed in 200 ms then climbed the idle ladder, a cluster that begins on the date a
448
+ specific commit merged.
449
+ 3. Rank what you would file by LOSS, not by run count: the digest's time-and-spend table and
450
+ top loss causes price each cause in agent-hours and dollars, and a cluster that burned hours
451
+ outranks one that burned seconds — cite its cost when it is the reason you file.
452
+ 4. Before filing, dedupe: check the "Previously filed from this install" block in your prompt
453
+ and run \`gh issue list --repo znation/tumwater --search <cluster terms>\` — never file a
454
+ duplicate. When the same cluster is already filed, there is nothing to do.
455
+ 5. File it with \`gh issue create --repo znation/tumwater --title … --body …\` from the shell
456
+ and verify the issue URL in the command's output before you report it. The body cites the
457
+ cluster's normalized key, its count and roles, the correlated \`merged\` commit, and the
458
+ output of \`tumwater version\`; redact absolute paths and secrets — file only what the digest
459
+ itself already shows.
460
+ You never edit files in this repo — \`gh\` is your only write. End every run with the
461
+ ${NOTHING_TO_DO} sentinel; when you filed, one \`FILED: #<n> — <title>\` line rides just above it
462
+ (named as qa's FLOW line is). A run that cannot file because \`gh\` is missing, unauthenticated,
463
+ or failed ends WITHOUT the sentinel, naming the failure in your final prose so tumwater's
464
+ finished-without-changes warning surfaces it. If every cluster is ordinary infrastructure weather
465
+ or already filed, there is nothing to do.`,
352
466
  },
353
467
  {
354
468
  id: "security",
@@ -461,6 +575,7 @@ arguments, config files, environment), and rough edges you meet while running th
461
575
  - promote a recurring non-\`none\` \`gap:\` tag — three or more Fixed entries carrying it —
462
576
  into a PLANS.md entry for the infrastructure that would retire it, citing those entries.
463
577
  ${VALIDATION_GAP_TALLY}
578
+ ${PLAN_QUEUE_GUIDANCE}
464
579
  You edit only markdown — never source.
465
580
 
466
581
  Reading the backlog files: PLANS.md and BUGS.md hold open work, so read them whole when your move
@@ -1,11 +1,10 @@
1
- // vocabulary: plain
2
1
  /** Shared instruction fragments role prompts and prompt assembly embed verbatim. Pure
3
2
  * data: this module imports only the dependency-free legacy-role-ids, so prompt assembly
4
3
  * can depend on it without a cycle.
5
4
  * Split from roles.ts, whose catalog (now role-catalog.ts) find texts embed these fragments
6
5
  * while prompt.ts embeds them too — the wording is one concern, the catalog another. */
7
6
  import { roleCommitGrep } from "./legacy-role-ids.js";
8
- /** Shared guidance for any loop about to record a plan or bug: split independent parts
7
+ /** Shared guidance for any role about to record a plan or bug: split independent parts
9
8
  * into their own entries. Defined once so the director and role prompts cannot drift.
10
9
  * Lives here (not in prompt.ts) because two role `find` texts embed it — keeping this
11
10
  * module import-free, with the dependency running one way: prompt assembly → roles,
@@ -14,8 +13,8 @@ export const DECOMPOSITION_GUIDANCE = `Before recording a plan or bug, check whe
14
13
  into independent subparts (separate features, or separate bugs). If it does, record each part as
15
14
  its own PLANS.md/BUGS.md entry that cross-references its siblings, so roles can pick them up
16
15
  independently; if the parts are not truly independent, keep a single entry.`;
17
- /** Shared instruction for closing a backlog entry (the feature loop moving a plan to Done,
18
- * the bugfix loop moving a bug to Fixed). Written against the observed failure (PLANS.md
16
+ /** Shared instruction for closing a backlog entry (the feature role moving a plan to Done,
17
+ * the bugfix role moving a bug to Fixed). Written against the observed failure (PLANS.md
19
18
  * 2026-09-30, part 1/3): "move it to a Done section" reads as "create a section", and five
20
19
  * commits left PLANS.md with two `## Done` headings — so the wording names the file's EXISTING
21
20
  * resolved heading, forbids touching `## ` structure, and gives the verifiable invariant
@@ -23,11 +22,16 @@ independently; if the parts are not truly independent, keep a single entry.`;
23
22
  * numbered step of their task. The pasted entry does not stay there: tumwater moves it to the top
24
23
  * of `archive` when the change commits (plans/backlog-archive.md), which the last line says so no
25
24
  * run opens the archive to do it by hand. Takes the file, section and archive names so the feature
26
- * and bugfix texts cannot drift apart; lives here with the other shared fragments. */
27
- export function backlogMoveGuidance(file, openSection, resolvedSection, archive) {
25
+ * and bugfix texts cannot drift apart; lives here with the other shared fragments. Also takes
26
+ * the closing token ("done" for a finished plan, "fixed" for a BUGS.md record) chosen by the
27
+ * caller, so the verb and the heading-key rule are named instead of left for the role to guess. */
28
+ export function backlogMoveGuidance(file, openSection, resolvedSection, archive, token) {
28
29
  return `move the entry, step by step:
29
30
  a. cut the entry out of \`${openSection}\`;
30
- b. add the closing date to its \`### \` heading;
31
+ b. add the closing stamp to its \`### \` heading, at the end — \`— ${token} YYYY-MM-DD by the <role> role\`
32
+ (a finished plan appends it after its existing \`(planned …)\` parenthetical; the heading
33
+ text before its first \` (\` must not change, or the gate reads it as a brand-new entry
34
+ under \`${resolvedSection}\` rather than a move);
31
35
  c. paste it as the first entry under the file's existing \`${resolvedSection}\` heading;
32
36
  d. if it was the last entry under \`${openSection}\`, leave a \`_None yet._\` placeholder
33
37
  there.
@@ -39,7 +43,7 @@ export function backlogMoveGuidance(file, openSection, resolvedSection, archive)
39
43
  * a comment inside the template would become prompt text. */
40
44
  const REQUIRES_PART_FORM = "requires part N/M landed"; // vocabulary-ok: requiredParts parses this heading syntax
41
45
  /** Shared sizing rule for anyone writing a plan (the plan role and the director): the feature
42
- * loop is one mid-sized local model working alone in one run, and the review gate rejects a
46
+ * role is one mid-sized local model working alone in one run, and the review gate rejects a
43
47
  * change that lands less than its entry promises — so a plan must fit that run. Oversized plans
44
48
  * were the pattern behind "half-done against its own plan" rejections. The last sentence is the
45
49
  * self-hosting build lag (BUGS.md 2026-09-23): plan 4b bundled "the merge preserves the live
@@ -58,6 +62,17 @@ ranges, because line anchors go stale with every merge. When tumwater is buildin
58
62
  every commit merges under the build that predates it, so a
59
63
  change to how merges behave and any step that depends on it are separate sub-plans, the second
60
64
  merge only once the first is the running build.`;
65
+ /** Shared guidance for anyone writing a plan (the plan role, the organize role, and the
66
+ * director): a new plan joins the BACK of the `## Planned` queue, never the top. Part of the
67
+ * define-once pattern of DECOMPOSITION_GUIDANCE/PLAN_SIZING — file order under `## Planned` IS
68
+ * the work queue: claim assignment takes the first free entry in file order (`assignNext`,
69
+ * src/scheduling/claims.ts), so an entry set at the top jumps ahead of work already waiting.
70
+ * Lives here because three role `find` texts and the director's routing bullet embed it. */
71
+ export const PLAN_QUEUE_GUIDANCE = `A new plan joins the queue at the BACK: append the entry at the
72
+ END of \`## Planned\`, after every existing entry, never at the top. File order under
73
+ \`## Planned\` is the work queue — claim assignment takes the first free entry in that order
74
+ (\`assignNext\`, src/scheduling/claims.ts) — so an entry inserted at the top jumps ahead of work
75
+ already waiting.`;
61
76
  /** The closed vocabulary for the validation-gap trace (plans/repair-traces.md): what made a bug
62
77
  * hard to *confirm*, which is the only evidence of where this project's test infrastructure is
63
78
  * weakest. Closed on purpose so the traces aggregate, and `none` is a written value rather than an
@@ -109,36 +124,66 @@ export const VALIDATION_GAP_QUERY = `grep -snE '\\*\\*Validation gap:\\*\\* (${V
109
124
  * parsing the date or the wording out of the full template — the same split as
110
125
  * NEEDS_REPLAN_PREFIX. */
111
126
  export const NEEDS_REVIEW_PREFIX = `**Needs review `;
112
- /** The markdown note the feature loop appends to a plan it cannot finish in one run, so the plan
113
- * loop can split it. Mirrors the **Refused …** note convention: a note the next fresh tick reads,
127
+ /** The tag text before the held count, shared by the note templates and the parser
128
+ * (backlog-eligibility's heldCount) so the two cannot drift: the live count on a Needs-review
129
+ * or Needs-replan note is written inline as `(held N)`, with `N` one past the surviving prior
130
+ * count (the feature role appends it, the plan role's split carries it via PRIOR_HELD_NOTE). */
131
+ export const HELD_TAG_PREFIX = `(held `;
132
+ /** The markdown note the feature role appends to a plan it cannot finish in one run, so the plan
133
+ * role can split it. Mirrors the **Refused …** note convention: a note the next fresh run reads,
114
134
  * with no code parsing it. Defined once so the feature and plan texts and the director's routing
115
- * clause cannot drift. */
116
- export const NEEDS_REVIEW_NOTE = `${NEEDS_REVIEW_PREFIX}<YYYY-MM-DD> by feature: too large for one run**`;
135
+ * clause cannot drift. The trailing `(held <N>)` tag (HELD_TAG_PREFIX) is the live held count,
136
+ * written as one past the count surviving on the entry; it sits inside the line, so
137
+ * NEEDS_REVIEW_PREFIX still recognizes the note. */
138
+ export const NEEDS_REVIEW_NOTE = `${NEEDS_REVIEW_PREFIX}<YYYY-MM-DD> by feature: too large for one run ${HELD_TAG_PREFIX}<N>)**`;
139
+ /** The literal reason the automatic convergence hold writes into a held plan's note
140
+ * (holdEntryAfterExhaustion, src/runner/revision.ts, builds the note as
141
+ * `**Needs review … by <role>: could not converge — <surviving objections>**`, INV-BH4).
142
+ * Exported so the writer and the reader share one literal: it sits between holdEntryAfterExhaustion
143
+ * (which writes the note) and the plan role's charter (role-catalog.ts `find`) which reads it, so
144
+ * reason to recognize the automatic hold as distinct from the feature role's too-large
145
+ * ${NEEDS_REVIEW_NOTE} note and the ${NEEDS_REPLAN_PREFIX} note — both carry no
146
+ * `could not converge` text, so the charter routes each hold to its own action without matching
147
+ * on a second literal. Add no second spelling of this text. */
148
+ export const CONVERGENCE_HOLD_MARKER = "could not converge";
149
+ /** The marker a lifted hold leaves behind, so a split or rewrite carries the count instead of
150
+ * letting it reset: `**Previously held N**` on the note's line. The live count is `(held N)` on
151
+ * the note; the surviving marker after a lift is `**Previously held N**` — heldCount reads the
152
+ * first as the priority, then the second. */
153
+ export const PRIOR_HELD_PREFIX = `**Previously held `;
154
+ /** The literal marker a lift leaves on a split or rewrite so the held count survives instead of
155
+ * resetting: a `**Previously held <N>**` line at the same count the lifted note carried (the
156
+ * <N> here is the value the note's `(held N)` tag held). heldCount reads this as the fallback
157
+ * when no live note is present, so the count round-trips a split. */
158
+ export const PRIOR_HELD_NOTE = `${PRIOR_HELD_PREFIX}<N>**`;
117
159
  /** The literal prefix every Needs-replan note starts with. Exported so a reader that only has to
118
160
  * recognize one (the backlog index's ` [blocked: needs replan]` mark) can match on it instead of
119
161
  * parsing the date or the round count out of the full template. */
120
162
  export const NEEDS_REPLAN_PREFIX = `**Needs replan `;
121
- /** The markdown note the feature loop appends under a plan whose change was rejected after its
122
- * final revision round, carrying the reviewer's objections back to the strong-tier plan loop
163
+ /** The markdown note the feature role appends under a plan whose change was rejected after its
164
+ * final revision round, carrying the reviewer's objections back to the strong-tier plan role
123
165
  * instead of letting feature re-author the same plan. Same convention as NEEDS_REVIEW_NOTE: a
124
- * note a later fresh tick reads, with no code parsing it. */
125
- export const NEEDS_REPLAN_NOTE = `${NEEDS_REPLAN_PREFIX}<YYYY-MM-DD> by feature: rejected after <N> review rounds**`;
166
+ * note a later fresh run reads, with no code parsing it. The trailing `(held <H>)` tag
167
+ * (HELD_TAG_PREFIX) is the live held count, one past the count surviving on the entry; the
168
+ * placeholder is `<H>` (for held) so it reads apart from the `<N>` review-round count earlier
169
+ * in the same line. It sits inside the line, so NEEDS_REPLAN_PREFIX still recognizes the note. */
170
+ export const NEEDS_REPLAN_NOTE = `${NEEDS_REPLAN_PREFIX}<YYYY-MM-DD> by feature: rejected after <N> review rounds ${HELD_TAG_PREFIX}<H>)**`;
126
171
  /** The literal prefix every Waiting-for-the-next-failure note starts with. Exported so a reader
127
172
  * that only has to recognize one (the backlog index's ` [blocked: waiting for the next failure]`
128
173
  * mark) can match on it instead of parsing the date or the wording out of the full template. */
129
174
  export const WAITING_FOR_FAILURE_PREFIX = `**Waiting for the next failure `;
130
- /** The markdown note the bugfix loop writes under an open bug it cannot reproduce once it has made
175
+ /** The markdown note the bugfix role writes under an open bug it cannot reproduce once it has made
131
176
  * the failure explain itself, so bugfix stops spending runs without new evidence. Same convention
132
- * as NEEDS_REPLAN_NOTE: a note a later fresh tick reads. It lifts when whoever records the next
177
+ * as NEEDS_REPLAN_NOTE: a note a later fresh run reads. It lifts when whoever records the next
133
178
  * occurrence deletes the line, or by `tumwater bug --release <n>`. */
134
179
  export const WAITING_FOR_FAILURE_NOTE = `${WAITING_FOR_FAILURE_PREFIX}<YYYY-MM-DD> by bugfix: <what the next failure's message will show>**`;
135
180
  /** How a role with no backlog (organize, clean, dedupe, perf, security, robustness, improve) finds
136
181
  * its one task, as numbered steps. Written against the observed failure: with nothing to point
137
182
  * at, a local model reads the codebase file by file — thirty whole-file reads, ~300 KB of tool
138
- * output — and then either fills its window or declares nothing-to-do (improve: 41 of 44 ticks in
183
+ * output — and then either fills its window or declares nothing-to-do (improve: 41 of 44 runs in
139
184
  * the first week of September landed nothing). Cheap signals shortlist candidates; a decision
140
185
  * deadline ends the search; the harness's own commit subjects (`tumwater(<role>): …`) are the
141
- * only cross-tick memory of what the role did lately, so the text names them. Takes the role id
186
+ * only cross-run memory of what the role did lately, so the text names them. Takes the role id
142
187
  * so the git command is literal, not a placeholder the model has to fill in. */
143
188
  export function searchGuidance(roleId) {
144
189
  return `How to search — do not read the codebase file by file: a whole-tree survey costs most of