tumwater 0.4.2 → 0.4.3

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 (305) hide show
  1. package/README.md +6 -6
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +6 -6
  4. package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
  5. package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
  6. package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
  7. package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
  8. package/dist/src/agent/backend-traits.js +2 -2
  9. package/dist/src/agent/claude/claude-account.js +1 -8
  10. package/dist/src/agent/claude/claude-backend.js +3 -3
  11. package/dist/src/agent/claude/claude-env.js +27 -31
  12. package/dist/src/agent/claude/claude-stream.js +111 -54
  13. package/dist/src/agent/codex/codex-account.js +9 -0
  14. package/dist/src/agent/codex/codex-backend.js +2 -2
  15. package/dist/src/agent/codex/codex-env.js +28 -15
  16. package/dist/src/agent/codex/codex-hook-trust.js +39 -20
  17. package/dist/src/agent/codex/codex-rollout.js +93 -56
  18. package/dist/src/agent/codex/codex-stream.js +1 -1
  19. package/dist/src/agent/env-scrub.js +18 -0
  20. package/dist/src/agent/image-input.js +68 -11
  21. package/dist/src/agent/notes-shim.js +2 -2
  22. package/dist/src/agent/opencode/opencode-backend.js +2 -2
  23. package/dist/src/agent/opencode/opencode-catalog.js +50 -27
  24. package/dist/src/agent/opencode/opencode-config.js +32 -14
  25. package/dist/src/agent/opencode/opencode-env.js +12 -13
  26. package/dist/src/agent/opencode/opencode-stream.js +47 -25
  27. package/dist/src/agent/session-ledger.js +1 -1
  28. package/dist/src/agent/subscription-account.js +3 -3
  29. package/dist/src/backlog/backlog-cli-view.js +81 -41
  30. package/dist/src/backlog/backlog-cli.js +57 -26
  31. package/dist/src/backlog/backlog-eligibility.js +58 -102
  32. package/dist/src/backlog/backlog-md.js +5 -5
  33. package/dist/src/backlog/backlog-parts.js +65 -27
  34. package/dist/src/backlog/backlog-release.js +12 -12
  35. package/dist/src/backlog/backlog-structure.js +114 -61
  36. package/dist/src/backlog/backlog.js +1 -1
  37. package/dist/src/baseline/main-baseline.js +148 -84
  38. package/dist/src/budget/budget-fallback-view.js +100 -243
  39. package/dist/src/budget/budget-wiring.js +29 -65
  40. package/dist/src/budget/fallback-breaker.js +2 -2
  41. package/dist/src/build/build-check-detect.js +43 -14
  42. package/dist/src/build/build-check-events.js +2 -2
  43. package/dist/src/build/build-check-scoped.js +40 -16
  44. package/dist/src/build/build-check.js +13 -5
  45. package/dist/src/cli/cli-command-args.js +89 -52
  46. package/dist/src/cli/cli-flag-specs.js +42 -22
  47. package/dist/src/cli/cli-init.js +34 -12
  48. package/dist/src/cli/cli-query-commands.js +1 -1
  49. package/dist/src/cli/cli-repo-gate.js +2 -2
  50. package/dist/src/cli/cli-start.js +91 -58
  51. package/dist/src/cli/completion.js +82 -46
  52. package/dist/src/cli/config-commands.js +82 -71
  53. package/dist/src/cli/help.js +4 -6
  54. package/dist/src/cli/log-commands.js +43 -23
  55. package/dist/src/cli/notify-command.js +2 -2
  56. package/dist/src/cli/question-commands.js +86 -60
  57. package/dist/src/cli.js +181 -135
  58. package/dist/src/collections.js +31 -16
  59. package/dist/src/concurrency/check-slots.js +70 -0
  60. package/dist/src/concurrency/lock.js +22 -0
  61. package/dist/src/concurrency/run-permit.js +49 -0
  62. package/dist/src/concurrency/semaphore.js +46 -250
  63. package/dist/src/config/config-editable-keys.js +4 -7
  64. package/dist/src/config/config-fallbacks.js +81 -101
  65. package/dist/src/config/config-field-checks.js +66 -37
  66. package/dist/src/config/config-keys.js +5 -20
  67. package/dist/src/config/config-live.js +4 -28
  68. package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
  69. package/dist/src/config/config-request.js +153 -102
  70. package/dist/src/config/config-schema.js +9 -11
  71. package/dist/src/config/config-selector-checks.js +1 -1
  72. package/dist/src/config/config-validation-agents.js +2 -2
  73. package/dist/src/config/config-validation-roles.js +4 -52
  74. package/dist/src/config/config-validation.js +4 -38
  75. package/dist/src/config/config-views.js +9 -34
  76. package/dist/src/config/config-write.js +6 -23
  77. package/dist/src/config/config.js +4 -5
  78. package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
  79. package/dist/src/doctor/doctor-backlog-plans.js +24 -24
  80. package/dist/src/doctor/doctor-backlog.js +7 -5
  81. package/dist/src/doctor/doctor-check-names.js +3 -6
  82. package/dist/src/doctor/doctor-checks.js +5 -5
  83. package/dist/src/doctor/doctor-claude-checks.js +44 -28
  84. package/dist/src/doctor/doctor-codex-checks.js +6 -6
  85. package/dist/src/doctor/doctor-contention-checks.js +44 -112
  86. package/dist/src/doctor/doctor-fallback-checks.js +2 -2
  87. package/dist/src/doctor/doctor-holds.js +6 -45
  88. package/dist/src/doctor/doctor-install.js +1 -1
  89. package/dist/src/doctor/doctor-launch-services.js +1 -1
  90. package/dist/src/doctor/doctor-model-checks.js +178 -94
  91. package/dist/src/doctor/doctor-notify.js +2 -2
  92. package/dist/src/doctor/doctor-opencode-checks.js +5 -4
  93. package/dist/src/doctor/doctor-orphans.js +120 -20
  94. package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
  95. package/dist/src/doctor/doctor-pi-checks.js +2 -2
  96. package/dist/src/doctor/doctor-principles.js +3 -3
  97. package/dist/src/doctor/doctor-project-checks.js +3 -3
  98. package/dist/src/doctor/doctor-role-notes.js +1 -1
  99. package/dist/src/doctor/doctor.js +8 -11
  100. package/dist/src/events/event-format.js +30 -54
  101. package/dist/src/events/event-labels.js +1 -5
  102. package/dist/src/events/event-legacy.js +1 -3
  103. package/dist/src/events/{notify.js → event-notify.js} +2 -2
  104. package/dist/src/events/event-read.js +1 -1
  105. package/dist/src/failure/failure-cluster.js +6 -6
  106. package/dist/src/failure/failure-data.js +8 -4
  107. package/dist/src/failure/failure-render.js +44 -34
  108. package/dist/src/failure/failure-state-change.js +4 -34
  109. package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
  110. package/dist/src/files/files.js +30 -0
  111. package/dist/src/files/json-object.js +8 -8
  112. package/dist/src/files/stat-cache.js +12 -8
  113. package/dist/src/gates/budget-gates.js +85 -84
  114. package/dist/src/gates/gate-polls.js +27 -68
  115. package/dist/src/gates/pause-gates.js +16 -2
  116. package/dist/src/gates/readiness.js +1 -1
  117. package/dist/src/gates/role-cap-gates.js +44 -35
  118. package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
  119. package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
  120. package/dist/src/git/git.js +75 -45
  121. package/dist/src/git/worktree-pool.js +3 -6
  122. package/dist/src/gui/gui-admin-endpoints.js +3 -3
  123. package/dist/src/gui/gui-backlog-commands.js +4 -4
  124. package/dist/src/gui/gui-backlog-endpoints.js +61 -41
  125. package/dist/src/gui/gui-command.js +43 -24
  126. package/dist/src/gui/gui-history-endpoints.js +100 -52
  127. package/dist/src/gui/gui-operator-commands.js +22 -23
  128. package/dist/src/gui/gui-prompt-commands.js +23 -23
  129. package/dist/src/gui/gui-report-endpoints.js +1 -1
  130. package/dist/src/history/history-data.js +71 -44
  131. package/dist/src/history/history-queued.js +49 -27
  132. package/dist/src/history/history.js +51 -33
  133. package/dist/src/inbox/prompt-commands.js +59 -42
  134. package/dist/src/init/init.js +88 -40
  135. package/dist/src/merge/merge-backlog-conflicts.js +1 -1
  136. package/dist/src/merge/merge-core.js +1 -1
  137. package/dist/src/merge/merge-drain.js +8 -11
  138. package/dist/src/merge/merge-pipeline.js +6 -7
  139. package/dist/src/merge/merge-post-review-drain.js +10 -13
  140. package/dist/src/merge/merge-requeue.js +1 -1
  141. package/dist/src/merge/merge-slot.js +0 -8
  142. package/dist/src/merge/merge-verify.js +8 -18
  143. package/dist/src/notes-cli.js +52 -42
  144. package/dist/src/operator/operator-commands.js +76 -44
  145. package/dist/src/operator/operator-intent.js +3 -3
  146. package/dist/src/orchestrator/orchestrator-launch.js +31 -63
  147. package/dist/src/orchestrator/orchestrator-poll.js +27 -32
  148. package/dist/src/orchestrator/orchestrator.js +8 -21
  149. package/dist/src/orchestrator/scheduling-admit.js +101 -68
  150. package/dist/src/orchestrator/scheduling-scope.js +1 -1
  151. package/dist/src/orchestrator/scheduling-skip.js +1 -1
  152. package/dist/src/orchestrator/startup-migrations.js +1 -1
  153. package/dist/src/paths.js +8 -7
  154. package/dist/src/pi/pi-args.js +5 -2
  155. package/dist/src/pi/pi-backend.js +2 -2
  156. package/dist/src/pi/pi-bin.js +3 -3
  157. package/dist/src/pi/pi-event-line.js +1 -1
  158. package/dist/src/pi/pi-models.js +43 -29
  159. package/dist/src/pi/pi-settings.js +43 -20
  160. package/dist/src/pi/pi-stream.js +32 -6
  161. package/dist/src/pi/pi-watchdogs.js +12 -5
  162. package/dist/src/pi/pi.js +2 -2
  163. package/dist/src/pi-extension/context-shake.js +32 -20
  164. package/dist/src/pi-extension/kill-guard.js +69 -0
  165. package/dist/src/process/process-group.js +49 -5
  166. package/dist/src/process/process.js +8 -8
  167. package/dist/src/process/run-marker.js +31 -13
  168. package/dist/src/process/supervisor.js +40 -28
  169. package/dist/src/prompt/dialect.js +1 -1
  170. package/dist/src/prompt/gate-notes.js +2 -2
  171. package/dist/src/prompt/notes-backlog.js +66 -37
  172. package/dist/src/prompt/principles.js +6 -4
  173. package/dist/src/prompt/prompt-followup.js +12 -4
  174. package/dist/src/prompt/prompt.js +88 -38
  175. package/dist/src/report/report-data.js +53 -37
  176. package/dist/src/report/report-fold.js +16 -9
  177. package/dist/src/report/report-render.js +21 -194
  178. package/dist/src/report/report-since-data.js +4 -3
  179. package/dist/src/report/report-since-render.js +173 -0
  180. package/dist/src/report/report-usage-fold.js +8 -8
  181. package/dist/src/report/report.js +46 -22
  182. package/dist/src/review/post-merge-review-render.js +9 -3
  183. package/dist/src/review/post-merge-review.js +1 -1
  184. package/dist/src/review/review-precheck.js +11 -1
  185. package/dist/src/review/review.js +40 -153
  186. package/dist/src/review/secret-leaks.js +26 -16
  187. package/dist/src/roles/role-catalog.js +11 -3
  188. package/dist/src/roles/role-guidance.js +16 -12
  189. package/dist/src/roles/role-render.js +1 -14
  190. package/dist/src/roles/role-view.js +9 -21
  191. package/dist/src/run/acceptance-greps.js +88 -45
  192. package/dist/src/run/run-apply.js +12 -6
  193. package/dist/src/run/run-detail-data.js +121 -68
  194. package/dist/src/run/run-detail-render.js +92 -35
  195. package/dist/src/run/run-detail.js +72 -41
  196. package/dist/src/run/run-prompt.js +63 -50
  197. package/dist/src/run/run-resume.js +76 -29
  198. package/dist/src/run/run-stage.js +135 -84
  199. package/dist/src/run/run-timing.js +8 -2
  200. package/dist/src/run/run-usage.js +31 -1
  201. package/dist/src/run/run-verdict.js +119 -50
  202. package/dist/src/run/stage-check.js +49 -32
  203. package/dist/src/run/stage-checks-shared.js +28 -0
  204. package/dist/src/run/stage-name-checks.js +111 -92
  205. package/dist/src/run/stage-path-checks.js +69 -42
  206. package/dist/src/run/stage-prose-checks.js +55 -40
  207. package/dist/src/runner/runner-outcome.js +6 -1
  208. package/dist/src/runner/runner-pi.js +3 -10
  209. package/dist/src/runner/runner-recovery.js +1 -1
  210. package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
  211. package/dist/src/runner/runner-run.js +97 -35
  212. package/dist/src/runner/runner-state-load.js +12 -9
  213. package/dist/src/runner/runner.js +41 -25
  214. package/dist/src/scheduling/claims.js +0 -4
  215. package/dist/src/scheduling/scheduling.js +53 -66
  216. package/dist/src/status/status-data.js +2 -2
  217. package/dist/src/status/status-role-notes.js +2 -2
  218. package/dist/src/status/status-roles.js +9 -17
  219. package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
  220. package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
  221. package/dist/src/team/team-hold.js +98 -65
  222. package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
  223. package/dist/src/team/team-polls.js +28 -126
  224. package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
  225. package/dist/src/team/team-state.js +4 -13
  226. package/dist/src/text/markdown.js +1 -1
  227. package/dist/src/text/phrases.js +20 -15
  228. package/dist/src/text/text.js +3 -3
  229. package/dist/src/ui/alert-feeds.js +49 -73
  230. package/dist/src/ui/badges.js +3 -13
  231. package/dist/src/ui/gui/gui-client-boot.js +2 -2
  232. package/dist/src/ui/gui/gui-client-composer.js +1 -1
  233. package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
  234. package/dist/src/ui/gui/gui-client-drawer.js +1 -1
  235. package/dist/src/ui/gui/gui-client-history.js +28 -14
  236. package/dist/src/ui/gui/gui-client-model.js +0 -12
  237. package/dist/src/ui/gui/gui-client-pending.js +7 -6
  238. package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
  239. package/dist/src/ui/gui/gui-client-report.js +1 -1
  240. package/dist/src/ui/gui/gui-client-roles.js +0 -1
  241. package/dist/src/ui/gui/gui-client-settings.js +43 -84
  242. package/dist/src/ui/gui/gui-client-team.js +4 -4
  243. package/dist/src/ui/gui/gui-client.js +7 -1
  244. package/dist/src/ui/progress-data.js +53 -40
  245. package/dist/src/ui/run-progress-model.js +9 -2
  246. package/dist/src/ui/status-model.js +2 -24
  247. package/dist/src/ui/status-payload.js +3 -7
  248. package/dist/src/ui/status-render.js +5 -9
  249. package/dist/src/ui/team-alert-backlog.js +6 -6
  250. package/dist/src/ui/team-alert-status.js +9 -170
  251. package/dist/src/ui/team-alert-tail.js +19 -215
  252. package/dist/src/ui/team-alerts.js +7 -8
  253. package/dist/src/ui/transcript-run.js +27 -16
  254. package/dist/src/ui/tui/tui-editor-answer.js +12 -10
  255. package/dist/src/ui/tui/tui-editor-attach.js +4 -3
  256. package/dist/src/ui/tui/tui-editor-backends.js +4 -3
  257. package/dist/src/ui/tui/tui-editor-brief.js +6 -9
  258. package/dist/src/ui/tui/tui-editor-budget.js +4 -3
  259. package/dist/src/ui/tui/tui-editor-config.js +80 -68
  260. package/dist/src/ui/tui/tui-editor-entry.js +12 -20
  261. package/dist/src/ui/tui/tui-editor-pause.js +51 -57
  262. package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
  263. package/dist/src/ui/tui/tui-editor-queued.js +11 -14
  264. package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
  265. package/dist/src/ui/tui/tui-editor-release.js +4 -3
  266. package/dist/src/ui/tui/tui-editor-retire.js +7 -7
  267. package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
  268. package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
  269. package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
  270. package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
  271. package/dist/src/ui/tui/tui-editor-search.js +4 -3
  272. package/dist/src/ui/tui/tui-editor-session.js +17 -4
  273. package/dist/src/ui/tui/tui-editors.js +1 -7
  274. package/dist/src/ui/tui/tui-frame.js +182 -134
  275. package/dist/src/ui/tui/tui-guard.js +20 -0
  276. package/dist/src/ui/tui/tui-input.js +159 -92
  277. package/dist/src/ui/tui/tui-keymap.js +51 -29
  278. package/dist/src/ui/tui/tui-keys.js +1 -1
  279. package/dist/src/ui/tui/tui-pane-activity.js +3 -3
  280. package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
  281. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  282. package/dist/src/ui/tui/tui-pane-health.js +252 -228
  283. package/dist/src/ui/tui/tui-pane-history.js +61 -70
  284. package/dist/src/ui/tui/tui-pane-pending.js +284 -247
  285. package/dist/src/ui/tui/tui-pane-queued.js +25 -27
  286. package/dist/src/ui/tui/tui-pane-report.js +2 -1
  287. package/dist/src/ui/tui/tui-pane-session.js +11 -7
  288. package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
  289. package/dist/src/ui/tui/tui-pane.js +14 -11
  290. package/dist/src/ui/tui/tui-panes.js +29 -32
  291. package/dist/src/ui/tui/tui.js +3 -4
  292. package/dist/src/verdict/fix-claim.js +76 -54
  293. package/dist/src/verdict/refusal.js +1 -1
  294. package/package.json +5 -5
  295. package/dist/src/concurrency/provider-lease.js +0 -105
  296. package/dist/src/runner/model-fallback.js +0 -76
  297. package/dist/src/runner/runner-fallback.js +0 -66
  298. package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
  299. package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
  300. /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
  301. /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
  302. /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
  303. /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
  304. /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
  305. /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
@@ -263,12 +263,13 @@ function sourceHaystackSignature(root) {
263
263
  sigs.sort();
264
264
  return sigs.join("|");
265
265
  }
266
- /** Every file under the source dirs the haystack reads (src/, test/, scripts/), node_modules and
267
- * dist trees skipped at any depth. */
266
+ /** Every file under the tree the haystack reads — the whole root, so a project whose code lives
267
+ * outside src//test//scripts/ (tools/, tests/, lib/, …) backs its symbols like a src/ one — with
268
+ * build output, vendored code, VCS and harness state skipped at any depth: node_modules, dist,
269
+ * .git and .tumwater. */
268
270
  function walkSourceFiles(root, visit) {
269
- const skipDirs = new Set(["node_modules", "dist"]);
270
- for (const dir of ["src", "test", "scripts"])
271
- walkFiles(join(root, dir), visit, skipDirs);
271
+ const skipDirs = new Set(["node_modules", "dist", ".git", ".tumwater"]);
272
+ walkFiles(root, visit, skipDirs);
272
273
  }
273
274
  /** A slash-free name with a file extension: `status-roles.ts`, not `src/a.ts` or `signalTree`. */
274
275
  const FILE_NAME_RE = /^[^/]+\.[A-Za-z][\w-]*$/;
@@ -297,23 +298,15 @@ export function unbackedSymbols(root, symbols, haystack) {
297
298
  export function missingSymbolNames(missing) {
298
299
  return missing.length <= 3 ? missing.join(", ") : `${missing.slice(0, 3).join(", ")}…`;
299
300
  }
300
- /** The first false-fix claim in an md-only diff that touches BUGS.md, or undefined when the
301
- * diff backs its Fixed transitions (or makes none). `files` is the ahead-of-main path list
302
- * of the diff merging on `mainBranch`; the worktree `wt` holds the tree being merged. */
303
- export async function falseFixReason(wt, mainBranch, files) {
304
- // Fixed entries live in BUGS.md's own section and in its archive, BUGS-FIXED.md
305
- // (plans/backlog-archive.md): tumwater moves a fixed bug there right before the commit, so a
306
- // fix claim reaches the gate as an archive edit, and an archived entry whose body is intact
307
- // is no claim at all. Both sides of the comparison read both files.
308
- if (!files.includes("BUGS.md") && !files.includes(BUGS_ARCHIVE))
309
- return undefined;
310
- // A deleted, unreadable, or non-regular (symlinked) file reads as empty: there is no Fixed
311
- // transition to verify, so this check passes and lets the gate's sibling structural check
312
- // (backlog-structure.ts) report the dropped section. Reading a symlink raw would pull an
313
- // outside host file's `## Fixed` heading into this gate's verdict (the same lstat policy the
314
- // backlog-conflict resolver and backlogStructureReason pin under INV-G3), so a head is read
315
- // only when lstat shows a regular file; ENOENT and symlinks both read as empty.
316
- const heads = ["BUGS.md", BUGS_ARCHIVE].map((f) => {
301
+ /** The `## Fixed` heads of BUGS.md and its archive as read under the lstat policy: a deleted,
302
+ * unreadable, or non-regular (symlinked) file reads as empty — there is no Fixed transition to
303
+ * verify, so the gate's sibling structural check (backlog-structure.ts) reports the dropped
304
+ * section instead. Reading a symlink raw would pull an outside host file's `## Fixed` heading
305
+ * into the gate's verdict (the same lstat policy the backlog-conflict resolver and
306
+ * backlogStructureReason pin under INV-G3), so a head is read only when lstat shows a regular
307
+ * file; ENOENT and symlinks both read as empty. */
308
+ function readFixedHeads(wt) {
309
+ return ["BUGS.md", BUGS_ARCHIVE].map((f) => {
317
310
  const full = join(wt, f);
318
311
  try {
319
312
  if (!lstatSync(full).isFile())
@@ -324,40 +317,40 @@ export async function falseFixReason(wt, mainBranch, files) {
324
317
  }
325
318
  return readTextOrNull(full) ?? "";
326
319
  });
327
- if (!heads.some((head) => head.includes("## Fixed")))
328
- return undefined;
329
- // The diff's own base, not main's tip: the gate diffs `mainBranch...HEAD` against the
330
- // merge-base, and the entry's Open/Fixed state lives in the tree the change builds on —
331
- // pre-batch main can still carry the record as (falsely) Fixed and make an Open→Fixed
332
- // restoration in a stacked batch compare as already done (BUGS.md 2026-09-23).
320
+ }
321
+ /** The base Fixed section bodies the diff's own merge-base holds, keyed by normalized heading.
322
+ * The base is the diff's own merge-base, not main's tip: the gate diffs `mainBranch...HEAD`
323
+ * against the merge-base, and the entry's Open/Fixed state lives in the tree the change builds
324
+ * on — pre-batch main can still carry the record as (falsely) Fixed and make an Open→Fixed
325
+ * restoration in a stacked batch compare as already done (BUGS.md 2026-09-23). Every base body
326
+ * under a heading counts: two distinct entries can share one normalized heading, and keeping
327
+ * only the last made the other read as rewritten (BUGS.md 2026-10-23). */
328
+ async function baseFixedBodies(wt, mainBranch) {
333
329
  const baseRev = await changeBaseRev(wt, mainBranch);
334
330
  const bases = [await fileContentAt(wt, baseRev, "BUGS.md"), await fileContentAt(wt, baseRev, BUGS_ARCHIVE)];
335
- // An already-Fixed entry is skipped only when its body is unchanged: an md-only edit that
336
- // rewrites an existing Fixed record's narrative must face the symbol check too (BUGS.md
337
- // 2026-09-23 — the in-place narrative rewrite is how a second phantom merge evaded it).
338
- // Every base body under a heading counts: two distinct entries can share one normalized
339
- // heading, and keeping only the last made the other read as rewritten (BUGS.md 2026-10-23).
340
- const baseBodies = new Map();
331
+ const bodies = new Map();
341
332
  for (const e of bases.flatMap((base) => fixedEntries(base))) {
342
333
  const key = normalizeFixedHeading(e.title);
343
- const bodies = baseBodies.get(key) ?? new Set();
344
- bodies.add(e.body);
345
- baseBodies.set(key, bodies);
334
+ const known = bodies.get(key) ?? new Set();
335
+ known.add(e.body);
336
+ bodies.set(key, known);
346
337
  }
347
- // Built lazily, only once a Fixed record's body actually changed: the haystack walks and
348
- // reads the entire source tree (~5 MB here), and the common BUGS.md edit moves nothing to
349
- // Fixed — an Open bug added, a narrative touched elsewhere — so the gate and the in-lock
350
- // recheck each paid that walk for a check that never reached a symbol.
351
- let haystack;
352
- const sections = heads.map((head) => fixedSection(head));
353
- const entries = sections.flatMap((section) => section.entries);
354
- const records = sections.flatMap((section) => section.records);
338
+ return { bases, bodies };
339
+ }
340
+ /** A changed full Fixed entry's rejection, or undefined when every changed entry backs its
341
+ * symbols. An already-Fixed entry is skipped only when its body is unchanged: an md-only edit
342
+ * that rewrites an existing Fixed record's narrative must face the symbol check too (BUGS.md
343
+ * 2026-09-23 — the in-place narrative rewrite is how a second phantom merge evaded it). The
344
+ * haystack is built lazily — only once a Fixed record's body actually changed — because it
345
+ * walks and reads the entire source tree (~5 MB here), and the common BUGS.md edit moves
346
+ * nothing to Fixed: an Open bug added, a narrative touched elsewhere, so the gate and the
347
+ * in-lock recheck each paid that walk for a check that never reached a symbol. */
348
+ function movedEntryReason(wt, entries, bodies, getHaystack) {
355
349
  for (const { title: heading, body } of entries) {
356
- const knownBodies = baseBodies.get(normalizeFixedHeading(heading));
350
+ const knownBodies = bodies.get(normalizeFixedHeading(heading));
357
351
  if (knownBodies?.has(body))
358
352
  continue;
359
- haystack ??= sourceHaystack(wt);
360
- const missing = unbackedSymbols(wt, fixSymbols(body), haystack);
353
+ const missing = unbackedSymbols(wt, fixSymbols(body), getHaystack());
361
354
  if (missing.length === 0)
362
355
  continue;
363
356
  const names = missingSymbolNames(missing);
@@ -372,10 +365,15 @@ export async function falseFixReason(wt, mainBranch, files) {
372
365
  `paragraph now names code that exists nowhere on this tree: ${names} — include that ` +
373
366
  `code in the same commit, or leave the entry's text as it was`;
374
367
  }
375
- // One-line records are compressions of entries the base already holds as Fixed, and history
376
- // keeps a renamed symbol's old name, so a record the diff adds or edits may carry forward any
377
- // name the base's Fixed section already records. A name it introduces must exist on the tree:
378
- // a bug moved straight from Open into a one-line record faces the same check as a full entry.
368
+ return undefined;
369
+ }
370
+ /** An added or edited one-line Fixed record's rejection, or undefined when each record either
371
+ * reproduces a base line or carries only names the base already holds. One-line records are
372
+ * compressions of entries the base already holds as Fixed, and history keeps a renamed symbol's
373
+ * old name, so a record the diff adds or edits may carry forward any name the base's Fixed
374
+ * section already records. A name it introduces must exist on the tree: a bug moved straight
375
+ * from Open into a one-line record faces the same check as a full entry. */
376
+ function addedRecordReason(wt, records, bases, getHaystack) {
379
377
  const baseLines = bases.flatMap((base) => sectionLines(base, "Fixed")).map((line) => line.trim());
380
378
  const seen = new Set(baseLines);
381
379
  const baseText = baseLines.join("\n");
@@ -385,8 +383,7 @@ export async function falseFixReason(wt, mainBranch, files) {
385
383
  const introduced = fixSymbols(record).filter((symbol) => !baseText.includes(symbol));
386
384
  if (introduced.length === 0)
387
385
  continue;
388
- haystack ??= sourceHaystack(wt);
389
- const missing = unbackedSymbols(wt, introduced, haystack);
386
+ const missing = unbackedSymbols(wt, introduced, getHaystack());
390
387
  if (missing.length === 0)
391
388
  continue;
392
389
  return (`md-only BUGS.md edit adds the one-line Fixed record "${truncate(record, RECORD_QUOTE_MAX)}", ` +
@@ -396,3 +393,28 @@ export async function falseFixReason(wt, mainBranch, files) {
396
393
  }
397
394
  return undefined;
398
395
  }
396
+ /** The first false-fix claim in an md-only diff that touches BUGS.md, or undefined when the
397
+ * diff backs its Fixed transitions (or makes none). `files` is the ahead-of-main path list
398
+ * of the diff merging on `mainBranch`; the worktree `wt` holds the tree being merged. */
399
+ export async function falseFixReason(wt, mainBranch, files) {
400
+ // Fixed entries live in BUGS.md's own section and in its archive, BUGS-FIXED.md
401
+ // (plans/backlog-archive.md): tumwater moves a fixed bug there right before the commit, so a
402
+ // fix claim reaches the gate as an archive edit, and an archived entry whose body is intact
403
+ // is no claim at all. Both sides of the comparison read both files.
404
+ if (!files.includes("BUGS.md") && !files.includes(BUGS_ARCHIVE))
405
+ return undefined;
406
+ const heads = readFixedHeads(wt);
407
+ if (!heads.some((head) => head.includes("## Fixed")))
408
+ return undefined;
409
+ const { bases, bodies } = await baseFixedBodies(wt, mainBranch);
410
+ const sections = heads.map((head) => fixedSection(head));
411
+ const entries = sections.flatMap((section) => section.entries);
412
+ const records = sections.flatMap((section) => section.records);
413
+ // Built lazily, shared by both checks, only once a Fixed record's body actually changed: the
414
+ // haystack walks and reads the entire source tree (~5 MB here), and the common BUGS.md edit
415
+ // moves nothing to Fixed — an Open bug added, a narrative touched elsewhere — so the gate and
416
+ // the in-lock recheck each paid that walk for a check that never reached a symbol.
417
+ let haystack;
418
+ const getHaystack = () => (haystack ??= sourceHaystack(wt));
419
+ return movedEntryReason(wt, entries, bodies, getHaystack) ?? addedRecordReason(wt, records, bases, getHaystack);
420
+ }
@@ -106,7 +106,7 @@ async function stripBlockedPlanRefusalNotes(ctx, wt) {
106
106
  // and a plain writeFileSync that a kill interrupted would commit a truncated file,
107
107
  // permanently dropping the plan entries past the cut point. writeTextAtomic is the
108
108
  // harness's one write-to-temp-then-rename entry point — the same one backlog-write.ts
109
- // and question-answer.ts use for the markdown backlog files.
109
+ // and backlog-question-answer.ts use for the markdown backlog files.
110
110
  try {
111
111
  writeTextAtomic(plansFile, md);
112
112
  for (const title of dropped) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tumwater",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Opinionated autonomous development harness built on pi. You write the initial prompt; role-driven loops build the project.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,10 +37,10 @@
37
37
  "build": "rm -rf dist && tsc && node scripts/stamp-build.mjs",
38
38
  "prepack": "npm run build",
39
39
  "lint": "eslint . --cache",
40
- "test": "node scripts/live-checkout-guard.mjs && eslint . --cache && tsc --incremental && node scripts/stamp-build.mjs && node dist/test/test-runner.js",
41
- "test:gate": "node scripts/live-checkout-guard.mjs && eslint . --cache && tsc --incremental && node scripts/stamp-build.mjs && node dist/test/test-runner.js --affected",
42
- "test:e2e": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node dist/test/test-runner.js e2e",
43
- "test:coverage": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node dist/test/test-runner.js --coverage",
40
+ "test": "node scripts/live-checkout-guard.mjs && eslint . --cache && tsc --incremental && node scripts/stamp-build.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js",
41
+ "test:gate": "node scripts/live-checkout-guard.mjs && eslint . --cache && tsc --incremental && node scripts/stamp-build.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js --affected",
42
+ "test:e2e": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js e2e",
43
+ "test:coverage": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js --coverage",
44
44
  "smoke:claude": "node scripts/smoke-claude.mjs",
45
45
  "smoke:opencode": "node scripts/smoke-opencode.mjs",
46
46
  "smoke:codex": "node scripts/smoke-codex.mjs",
@@ -1,105 +0,0 @@
1
- /** The AsyncLocalStorage store that lets a run's lease follow its runs across providers
2
- * (plans/provider-concurrency.md §5): a lease rides here for the length of a run, a verify task,
3
- * or a resolver run, and `RunnerPi.runWithTransientRetry` re-keys it to the provider each run
4
- * actually uses. The one choke point every run a role makes (authoring, gate, merge and resolver)
5
- * passes through, so a budget handback or a model-failure switch that moves a run in flight to
6
- * another provider moves the lease with it instead of leaving it parked on the key it was admitted
7
- * on. The check-permit.ts `holdingPermit` store is the pattern: nothing here is a separate permit,
8
- * only the single lease the run path already holds.
9
- *
10
- * The store carries the `piDefault` (the caller's cached readPiDefaultModel result) beside the
11
- * lease so `ensureProviderLease` can resolve a bare no-model pi seam's key exactly as the
12
- * admission/review seam did — a run whose key were computed without it would re-key a default
13
- * team's run off pi's own provider. It also carries the semaphore and, optionally, wait
14
- * hooks, so a re-key that has to park on a full target key reports its wait the way a parked
15
- * resolver does (merge-verify.ts's providerWaitStage). */
16
- import { AsyncLocalStorage } from "node:async_hooks";
17
- import { seamConcurrencyKey } from "../config/config-views.js";
18
- const providerLeaseStore = new AsyncLocalStorage();
19
- /** Run `run` with `lease` in the provider-lease store, so any run it makes re-keys that lease to
20
- * the provider it actually uses (ensureProviderLease). `opts.piDefault` is the caller's cached pi
21
- * default model, so a bare no-model seam resolves to the same key the admission or review seam
22
- * used; `opts.hooks` (the verify: providerWaitStage) hears a re-key wait, if there is one. The same
23
- * lease object is released by the caller after the store is unwrapped — a re-key mutates it in
24
- * place, so release() frees whatever key the run path ended on. */
25
- export function withProviderLease(lease, semaphore, run, opts = {}) {
26
- return providerLeaseStore.run({ lease, semaphore, piDefault: opts.piDefault, hooks: opts.hooks }, run);
27
- }
28
- /** Re-key the calling lease to the provider `cfg` runs on, if it is not there yet. No-op when
29
- * the current async context carries no lease (a direct RunnerPi unit test, `tumwater history`) or
30
- * when the run is already on the lease's key. When it does re-key and `hooks` are present and
31
- * the target key is at its cap, the hooks hear `waiting` before the lease parks and `granted`
32
- * once the key frees — a re-key never waits on the team total (it already holds a team
33
- * permit), only on the target key's headroom. */
34
- export async function ensureProviderLease(cfg) {
35
- const ctx = providerLeaseStore.getStore();
36
- if (!ctx)
37
- return;
38
- const key = seamConcurrencyKey(cfg, ctx.piDefault);
39
- if (ctx.lease.key === key)
40
- return;
41
- // The leased currently holds `old` (or nothing); the target key's in-use does not include it,
42
- // so the key being at its cap is exactly whether rekey() will park.
43
- const parks = ctx.hooks !== undefined && keyAtCap(ctx, key);
44
- if (parks)
45
- ctx.hooks.waiting();
46
- await ctx.lease.rekey(key);
47
- if (parks)
48
- ctx.hooks.granted();
49
- }
50
- /** Whether `key` is at its configured cap right now (an absent or uncapped key is not) — the
51
- * same test semaphore.drive uses to withhold a re-key grant, decided from the live snapshot so
52
- * a re-key wait can be announced before the lease parks (providerWaitStage). */
53
- function keyAtCap(ctx, key) {
54
- if (key === undefined)
55
- return false;
56
- const entry = ctx.semaphore.snapshot().byKey[key];
57
- return entry?.limit !== undefined && entry.inUse >= entry.limit;
58
- }
59
- /** The pool-slot wait rule (plans/provider-concurrency.md §5 "No deadlock"): a lease never holds a
60
- * provider key while it waits for a worktree pool slot. Called by leaseSlot just before it parks on
61
- * a busy pool; it re-keys the calling lease to no key (which never waits), so a run or verify
62
- * waiting for a slot does not hold — and cannot deadlock — a provider slot whose in-use a re-key
63
- * waiter is waiting to drain. No-op without a lease in context. */
64
- export async function dropProviderLeaseKey() {
65
- const ctx = providerLeaseStore.getStore();
66
- if (!ctx)
67
- return;
68
- await ctx.lease.rekey(undefined);
69
- }
70
- /** Run `wait`, an idle stretch inside the calling lease's work, with the lease's team permit and
71
- * key slot handed back (SemaphoreLease.pause), so a parked run can use them meanwhile, then take
72
- * them back (resume) before returning. A failed review's in-place retry pause is the caller: with
73
- * one team permit, a reviewer that kept failing held it through two 30 s pauses and starved every
74
- * other role. Once `signal` has fired, nothing is taken back and the caller's release() settles a
75
- * still-paused lease. No-op without a lease in context. */
76
- export async function idleWithoutProviderLease(wait, signal) {
77
- const ctx = providerLeaseStore.getStore();
78
- if (!ctx)
79
- return wait();
80
- ctx.lease.pause();
81
- try {
82
- await wait();
83
- }
84
- finally {
85
- if (!signal?.aborted)
86
- await untilAborted(ctx.lease.resume(), signal);
87
- }
88
- }
89
- /** `p`, or an earlier return once `signal` fires: a resume parked on a full team must not keep an
90
- * aborted verification waiting for a permit it no longer needs. */
91
- async function untilAborted(p, signal) {
92
- if (!signal)
93
- return p;
94
- let onAbort = () => { };
95
- const aborted = new Promise((resolve) => {
96
- onAbort = resolve;
97
- signal.addEventListener("abort", onAbort, { once: true });
98
- });
99
- try {
100
- await Promise.race([p, aborted]);
101
- }
102
- finally {
103
- signal.removeEventListener("abort", onAbort);
104
- }
105
- }
@@ -1,76 +0,0 @@
1
- /** Consecutive provider-class failures that trip a fallback episode. */
2
- export const MODEL_FALLBACK_FAILURES = 3;
3
- /** The first probe cooldown after a trip; a failed probe doubles it. */
4
- export const MODEL_FALLBACK_COOLDOWN_MS = 5 * 60_000;
5
- /** The ceiling a repeatedly-failing probe's cooldown doubles toward. */
6
- export const MODEL_FALLBACK_MAX_COOLDOWN_MS = 30 * 60_000;
7
- /** True only while the role is in a fallback episode whose probe is still in the future — the
8
- * runs that use the fallback pair. */
9
- export function modelFallbackActive(state, now) {
10
- return state !== undefined && state.since > 0 && now < state.probeAt;
11
- }
12
- /** True only while the role is in a fallback episode whose cooldown has elapsed: the next
13
- * run uses the primary as the probe. */
14
- export function modelFallbackProbe(state, now) {
15
- return state !== undefined && state.since > 0 && now >= state.probeAt;
16
- }
17
- /** Classify one authoring agent run's verdict about the primary (src/run/run-usage.ts records the
18
- * same flags for the team-wide hold). An agent run that was aborted, quiet-killed, or timed out
19
- * — or that read an image its model cannot see (the runner killed it, plans/image-input.md) — is
20
- * inconclusive even when it also reports provider evidence: the harness killed it, so it is
21
- * no verdict about the primary's health. */
22
- export function modelFallbackVerdict(pi) {
23
- if (pi.aborted || pi.quietKilled || pi.timedOut || pi.imageBlind)
24
- return "inconclusive";
25
- if (!pi.ok && (pi.transientRateLimit || pi.transientBackend))
26
- return "provider-failure";
27
- return "answered";
28
- }
29
- /** Fold one authoring agent run's verdict into the role's fallback state, returning the next state
30
- * or undefined for "running the primary" (no episode).
31
- *
32
- * - Not in fallback, provider failure: increment the consecutive count; the
33
- * MODEL_FALLBACK_FAILURES-th trips the episode.
34
- * - Not in fallback, an answering agent run: clear the running count (undefined).
35
- * - Not in fallback, an inconclusive agent run: unchanged — a harness kill is not evidence the
36
- * primary recovered, so the running count survives it.
37
- * - In fallback, a fallback run (`probe` false): unchanged — its outcome neither returns
38
- * the role to primary nor re-trips it.
39
- * - In fallback, a probe run: an inconclusive probe is unchanged (retried on the next run);
40
- * an answering probe clears the episode; a provider-class failed probe doubles the cooldown
41
- * (capped) and schedules the next one.
42
- */
43
- export function recordModelFallback(state, outcome) {
44
- const { verdict, probe, now, reason } = outcome;
45
- if (state !== undefined && state.since > 0) {
46
- if (!probe)
47
- return state;
48
- if (verdict === "inconclusive")
49
- return state;
50
- if (verdict === "answered")
51
- return undefined;
52
- const cooldownMs = Math.min(state.cooldownMs * 2, MODEL_FALLBACK_MAX_COOLDOWN_MS);
53
- return { ...state, cooldownMs, probeAt: now + cooldownMs, reason: reason || state.reason };
54
- }
55
- if (verdict === "inconclusive")
56
- return state;
57
- if (verdict === "answered")
58
- return undefined;
59
- const failures = (state?.failures ?? 0) + 1;
60
- if (failures >= MODEL_FALLBACK_FAILURES) {
61
- return {
62
- failures: 0,
63
- since: now,
64
- probeAt: now + MODEL_FALLBACK_COOLDOWN_MS,
65
- cooldownMs: MODEL_FALLBACK_COOLDOWN_MS,
66
- reason: reason || "backend failure",
67
- };
68
- }
69
- return { failures, since: 0, probeAt: 0, cooldownMs: 0, reason };
70
- }
71
- /** The reason label one agent run's provider evidence yields: "rate-limit" for a 429, else the
72
- * backend-failure kind. Exported so the runner's evidence assembly and tests share one
73
- * spelling. */
74
- export function providerFailureReason(rateLimit, kind) {
75
- return rateLimit ? "rate-limit" : (kind ?? "backend failure");
76
- }
@@ -1,66 +0,0 @@
1
- import { logEventBestEffort } from "../events/events.js";
2
- import { modelFallbackVerdict, providerFailureReason, recordModelFallback } from "./model-fallback.js";
3
- /** Fold one AUTHORING agent run's verdict into the role's model-fallback state
4
- * (src/runner/model-fallback.ts) and emit the episode's started/ended events. Only the agent run
5
- * that actually happened is evidence: a run that never invoked pi (a skipped director
6
- * inbox, a recovered leftover, a merge-side `main_red` pin) returns from runOnce before this is
7
- * reached, and modelFallbackVerdict reports an aborted/killed/timed-out agent run as
8
- * inconclusive — so neither can end an episode without the primary having answered.
9
- * A role whose tier resolves to no fallback pair never trips, and an episode persisted from a
10
- * config that HAD a pair is cleared when a live edit removes it (or sets it to "pause"), so a
11
- * stale "on fallback" mark cannot outlive the config that created it. Persists on any state
12
- * change; the run's own finalize save carries it too. */
13
- export function foldModelFallbackPhase(ctx, fallbackCtx, pi) {
14
- const s = ctx.state;
15
- const prior = s.modelFallback;
16
- const now = Date.now();
17
- const verdict = modelFallbackVerdict(pi);
18
- const reason = providerFailureReason(pi.transientRateLimit, pi.backendKind);
19
- const wasIn = prior !== undefined && prior.since > 0;
20
- if (fallbackCtx.fallback === null) {
21
- // No fallback pair resolves any more: the feature is off for this role. End a persisted
22
- // episode (the primary just ran, so this run IS the return), and drop any pre-trip count
23
- // so a later config starts from zero. A killed/aborted/timed-out agent run is no verdict, so it
24
- // leaves the episode in place exactly as it does on a configured fallback.
25
- if (prior === undefined)
26
- return;
27
- if (wasIn && verdict === "inconclusive")
28
- return;
29
- emitFallbackEnded(ctx, prior, fallbackCtx.primary, now, wasIn);
30
- s.modelFallback = undefined;
31
- ctx.save();
32
- return;
33
- }
34
- const next = recordModelFallback(prior, { verdict, probe: fallbackCtx.probe, now, reason });
35
- const willBeIn = next !== undefined && next.since > 0;
36
- if (!wasIn && willBeIn) {
37
- logEventBestEffort(ctx.root, {
38
- source: ctx.role,
39
- type: "model_fallback_started",
40
- provider: fallbackCtx.fallback.provider,
41
- model: fallbackCtx.fallback.model,
42
- reason: next?.reason,
43
- });
44
- }
45
- else if (wasIn && !willBeIn && prior !== undefined) {
46
- emitFallbackEnded(ctx, prior, fallbackCtx.primary, now, wasIn);
47
- }
48
- if (next !== prior) {
49
- s.modelFallback = next;
50
- ctx.save();
51
- }
52
- }
53
- /** Log the model_fallback_ended event for an episode that was active. Split out so the two
54
- * ending paths — a successful probe and a config that dropped the pair — name the primary
55
- * pair and duration the same way. A pre-trip state (no episode) has no ended event. */
56
- function emitFallbackEnded(ctx, prior, primary, now, wasIn) {
57
- if (!wasIn)
58
- return;
59
- logEventBestEffort(ctx.root, {
60
- source: ctx.role,
61
- type: "model_fallback_ended",
62
- provider: primary.provider,
63
- model: primary.model,
64
- durationMs: now - (prior.since || now),
65
- });
66
- }
@@ -1,24 +0,0 @@
1
- import { loadConfigSafe } from "../../config/config.js";
2
- import { createTeamConfigKeyEditor } from "./tui-editor-config.js";
3
- /** The Ctrl+Y claude subscription-window reserve editor on the Activity view's shared prompt
4
- * line — the terminal twin of the dashboard Settings `Claude window reserve
5
- * (backends.claude.windowReserve)` field and `tumwater config set claudeWindowReserve`. Enter
6
- * writes the top-level `claudeWindowReserve`
7
- * through the shared setConfigKey/unsetConfigKey family, so validateConfig's claudeWindowReserve
8
- * block owns the value's truth. The team scope is the only scope, so the editor takes the line
9
- * on the Activity view (view 0) alone; a History pane keeps Ctrl+Y for its per-role Summary
10
- * toggle, which runs after this arm. */
11
- export function createClaudeReserveEditor(session) {
12
- return createTeamConfigKeyEditor(session, {
13
- mode: "claudeReserve",
14
- openCtrl: "y",
15
- configKey: "claudeWindowReserve",
16
- prefill: (s) => {
17
- const existing = loadConfigSafe(s.root).config?.claudeWindowReserve;
18
- return existing !== undefined ? JSON.stringify(existing) : "";
19
- },
20
- openFlash: "edit the team's claude subscription-window reserve (backends.claude.windowReserve, a JSON object like {\"seven_day\":0.8}; empty clears): Enter to save, Esc to cancel",
21
- clearedFlash: "claude window reserve cleared",
22
- setFlash: (text) => `claude window reserve set to ${text}`,
23
- });
24
- }
@@ -1,27 +0,0 @@
1
- import { loadConfigSafe } from "../../config/config.js";
2
- import { createTeamConfigKeyEditor } from "./tui-editor-config.js";
3
- /** The Ctrl+P provider-concurrency-caps editor on the Activity view's shared prompt line — the
4
- * terminal twin of the dashboard Settings `Provider concurrency caps (JSON)` field and
5
- * `tumwater config set maxConcurrentPerProvider`, completing the run-concurrency knob set
6
- * `tumwater doctor`'s checkProviderContention points at. Enter writes the top-level
7
- * `maxConcurrentPerProvider` through the shared setConfigKey/unsetConfigKey family the CLI and
8
- * the dashboard use — so setConfigKey JSON-parses the typed text and validateConfig's map block
9
- * owns the value's truth — and Esc, Ctrl+P again, and Ctrl+T cancel by restoring the prompt
10
- * draft. Mutually exclusive with the other editors on the one prompt line via the session's
11
- * held registry. The Team scope is the only scope, so the editor takes the line on the Activity
12
- * view (view 0) alone; a role's Transcript keeps Ctrl+P for its pause editor, which runs after
13
- * this arm. */
14
- export function createProviderCapsEditor(session) {
15
- return createTeamConfigKeyEditor(session, {
16
- mode: "providerCaps",
17
- openCtrl: "p",
18
- configKey: "maxConcurrentPerProvider",
19
- prefill: (s) => {
20
- const existing = loadConfigSafe(s.root).config?.maxConcurrentPerProvider;
21
- return existing !== undefined ? JSON.stringify(existing) : "";
22
- },
23
- openFlash: "edit the team's provider concurrency caps (maxConcurrentPerProvider, a JSON object like {\"anthropic\":2}; empty clears): Enter to save, Esc to cancel",
24
- clearedFlash: "provider concurrency caps cleared",
25
- setFlash: (text) => `provider concurrency caps set to ${text}`,
26
- });
27
- }
File without changes
File without changes