tumwater 0.2.0 → 0.3.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 (446) 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 +42 -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 +4 -3
  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 +402 -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 +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -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 +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -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-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -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 +1 -2
  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 +118 -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 +150 -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 +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,3 +1,4 @@
1
+ import { daysBetween } from "../text/datetime.js";
1
2
  /** The pure markdown layer of the backlog parsers: fence-aware reading of PLANS.md / BUGS.md /
2
3
  * QUESTIONS.md text, with no filesystem access — every function here takes the markdown text as
3
4
  * an argument. Split from src/backlog/backlog.ts, which keeps the stat-cached file readers
@@ -21,9 +22,9 @@ export function finishedArchiveOf(file) {
21
22
  return file === "PLANS.md" ? FINISHED_ARCHIVES[0] : FINISHED_ARCHIVES[1];
22
23
  }
23
24
  /** The repo-root backlog markdown files shared by every layer that knows them by name: the
24
- * tracked files loops edit and readers parse by `## ` section — read by backlog-structure.ts's
25
+ * tracked files roles edit and readers parse by `## ` section — read by backlog-structure.ts's
25
26
  * structural checks, merge/backlog-conflicts.ts's mechanical insert-conflict resolver, and
26
- * doctor-backlog.ts's duplicate-heading check. One definition, so the three cannot disagree
27
+ * doctor-backlog-plans.ts's duplicate-heading check. One definition, so the three cannot disagree
27
28
  * about which files count. The archive files count too: merges insert at their top, and their
28
29
  * `## ` heading is as load-bearing as the live files'. */
29
30
  export const BACKLOG_FILES = new Set([
@@ -133,8 +134,9 @@ export function nextSectionHeading(lines, from, fenced) {
133
134
  /** The body lines of one `## <sectionTitle>` section with fenced content stripped: the
134
135
  * sectionLines walk, then a fresh fenceTracker's filter — the shape every reader that
135
136
  * classifies a section's lines as markdown structure (entry headings, bullets, dates) walks.
136
- * Exactly two call sites today: entryDates (below) and backlog-structure.ts's
137
- * strandedPlanEntries. The fresh tracker is in sync with the document here: sectionLines
137
+ * Exactly three call sites today: entryDates (below), backlog-structure.ts's
138
+ * strandedPlanEntries, and stage-backlog-checks.ts's placeholderSections. The fresh
139
+ * tracker is in sync with the document here: sectionLines
138
140
  * only recognizes a `## ` boundary outside a fence, so the section's heading line is
139
141
  * non-fenced and the fence state at the section's first line is closed — a tracker started
140
142
  * there sees exactly what a document-wide tracker sees inside the section. Readers that
@@ -271,6 +273,51 @@ export function entryDates(md, sectionTitle, dateRe, first = false) {
271
273
  }
272
274
  return dates;
273
275
  }
276
+ /** The filing-to-done span of each finished entry inside one `## <sectionTitle>` section,
277
+ * paired with the day it completed on: `{ done, lagDays }` for every entry `entryDates` counts
278
+ * (`### ` headings and `- ` epitaphs whose trailing parenthetical carries a commit
279
+ * reference), an entry's `done` date being its LAST `dateRe` match and `filed` its FIRST
280
+ * `filingRe` match — the same completion/filing rules `entryDates`/`sectionFilingDates`
281
+ * apply (a `- ` epitaph reads both from its trailing parenthetical, exactly as entryDates
282
+ * does), so the report's lag read cannot disagree with the completion and filing reads about
283
+ * which date belongs to the entry. `filingRe` is a parameter so the backlog module's
284
+ * STAMP_DATE_RE keeps its one home; `dateRe` is cloned with the g flag (like lastDate) so
285
+ * matchAll sees every date. An entry with no filing stamp, or a filing later than its
286
+ * completion, contributes nothing. The span is whole calendar days via `daysBetween`. */
287
+ export function entryLags(md, sectionTitle, dateRe, filingRe) {
288
+ const lags = [];
289
+ const lines = sectionBodyLines(md, sectionTitle);
290
+ for (let i = 0; i < lines.length; i++) {
291
+ const line = lines[i] ?? "";
292
+ if (!line.startsWith("### ") && !line.startsWith("- "))
293
+ continue;
294
+ const meta = headingMetadata(lines, i);
295
+ let done = null;
296
+ let filed = null;
297
+ if (line.startsWith("- ")) {
298
+ // Same epitaph guard as entryDates: the completion must live in the trailing
299
+ // parenthetical beside a commit reference, and the filing read follows the same
300
+ // rule — sectionFilingDates matches the first stamp inside that same parenthetical
301
+ // for a `- ` bullet, so a description-date mention before it must not pair.
302
+ const tail = trailingParenthetical(line);
303
+ if (/\bcommits?\b/.test(tail)) {
304
+ done = lastDate(tail, dateRe);
305
+ filed = firstDate(tail, filingRe);
306
+ }
307
+ }
308
+ else {
309
+ done = lastDate(meta.text, dateRe);
310
+ filed = firstDate(meta.text, filingRe);
311
+ }
312
+ if (done !== null && filed !== null) {
313
+ const lagDays = daysBetween(filed, done);
314
+ if (lagDays >= 0)
315
+ lags.push({ done, lagDays });
316
+ }
317
+ i = meta.next - 1; // The loop's ++ resumes at the first line not consumed as metadata.
318
+ }
319
+ return lags;
320
+ }
274
321
  /** A `### `/`- ` entry start's METADATA, joined for matching: the start line plus, for `### `
275
322
  * headings only, continuation lines up to and including the first line ending in `)` (capped
276
323
  * at 3 lines) — wrapped headings carry their date on the second line, while `- ` epitaphs are
@@ -310,7 +357,7 @@ export function headingMetadata(lines, start) {
310
357
  * and silently dropped the epitaph's date from the day report (BUGS.md 2026-09-29). Unbalanced
311
358
  * text (no matching open paren) yields "" — the guard then treats the bullet as body text, as
312
359
  * before. The one home for this rule: entryDates reads it for `- ` epitaphs and
313
- * backlog-eligibility.ts's requiredParts for a heading's prerequisite clause (falling back to
360
+ * backlog-parts.ts's requiredParts for a heading's prerequisite clause (falling back to
314
361
  * openParenthetical when the heading's parenthetical wraps past the line). */
315
362
  export function trailingParenthetical(line) {
316
363
  if (!line.endsWith(")"))
@@ -361,7 +408,8 @@ export function lastDate(text, dateRe) {
361
408
  * (backlog.ts) applies to a heading line, so the filing read and `plan|bug --list`'s age agree
362
409
  * about when an entry opened. dateRe is cloned with the g flag so matchAll sees every date; a
363
410
  * text with no date yields null. Module-local: entryDates (its only caller) selects it with
364
- * `first`, so the walker stays single-homed like lastDate's completion rule. */
411
+ * `first`, so the walker stays single-homed like lastDate's completion rule (entryLags reads
412
+ * it directly for an entry's filing stamp). */
365
413
  function firstDate(text, dateRe) {
366
414
  const global = new RegExp(dateRe.source, dateRe.flags.includes("g") ? dateRe.flags : `${dateRe.flags}g`);
367
415
  const match = global.exec(text);
@@ -0,0 +1,254 @@
1
+ /** Backlog series parts: the `Series, part i/n` identity of a plan heading and the dependency
2
+ * graph the `requires parts … landed` clauses form among the planned entries. Pure over heading
3
+ * text and entry lists; the hold rules built on them live in backlog-eligibility.ts. */
4
+ import { collapseWhitespace } from "../text/text.js";
5
+ import { openParenthetical, trailingParenthetical } from "./backlog-md.js";
6
+ import { stripEntryStamp } from "./backlog-structure.js";
7
+ /** One prerequisite ref, resolved against the entry's own series when it names none. */
8
+ function parseRef(chunk, ownSeries) {
9
+ const text = chunk.trim().replace(/^parts?\s+/i, "");
10
+ if (text === "")
11
+ return [];
12
+ const m = /^(?:([\s\S]+?)\s+)?([0-9]+[a-z]?)\/(\d+)(?:\s*[–-]\s*(\d+)\/(\d+))?$/i.exec(text);
13
+ if (m === null)
14
+ return [];
15
+ const series = (m[1]?.trim() ?? "") || ownSeries;
16
+ if (series === null || series === "")
17
+ return [];
18
+ const of = Number(m[3]);
19
+ const start = m[2].toLowerCase();
20
+ if (!Number.isInteger(of) || of < 1)
21
+ return [];
22
+ if (m[4] === undefined)
23
+ return [{ series, part: start, of }];
24
+ // A range is numeric on both ends (the lettered parts never range); expand it inclusively.
25
+ const from = Number(start);
26
+ const to = Number(m[4]);
27
+ if (!Number.isInteger(from) || !Number.isInteger(to) || to < from)
28
+ return [];
29
+ const parts = [];
30
+ for (let p = from; p <= to; p++)
31
+ parts.push({ series, part: String(p), of });
32
+ return parts;
33
+ }
34
+ /** The stable identity of an entry heading: its `(planned …)`/`(done …)` stamp suffix
35
+ * removed through the shared stripEntryStamp, whitespace collapsed, lowercased. Adding a
36
+ * done stamp or a Refused note therefore leaves the key unchanged. */
37
+ export function entryKey(title) {
38
+ return collapseWhitespace(stripEntryStamp(title)).toLowerCase();
39
+ }
40
+ /** An entry's own series and part from its heading, or null when the heading is not a
41
+ * `<Series>, part i/n: …` title. The series is the text before `, part i/n:`. */
42
+ export function seriesPart(title) {
43
+ const m = /^(.+?),\s*part\s+([0-9]+[a-z]?)\/(\d+)\s*:/i.exec(stripEntryStamp(title));
44
+ if (m === null)
45
+ return null;
46
+ const series = m[1].trim();
47
+ if (series === "")
48
+ return null;
49
+ return { series, part: m[2].toLowerCase(), of: Number(m[3]) };
50
+ }
51
+ /** The one identity a PartRef matches by: lowercased series, verbatim part token. */
52
+ export function partKey(series, part) {
53
+ return `${series.toLowerCase()}\u0000${part}`;
54
+ }
55
+ /** The prerequisite-set identities of the still-planned entries — seriesPart(title) of each,
56
+ * keyed by partKey — built once for a batch and reused across every entry judged against it, so
57
+ * judging n entries is n seriesPart parses rather than n² (entryHold rebuilt the set per entry).
58
+ * Series compare case-insensitively and part tokens verbatim, exactly as entryHoldAgainst
59
+ * matches them. */
60
+ export function plannedPartKeys(planned) {
61
+ const keys = new Set();
62
+ for (const p of planned) {
63
+ const part = seriesPart(p.title);
64
+ if (part !== null)
65
+ keys.add(partKey(part.series, part.part));
66
+ }
67
+ return keys;
68
+ }
69
+ /** The planned parts a cancel released: the 1-based positions and stamp-free titles of the entries
70
+ * in `planned` (read after the cancel) whose requiredParts name the cancelled title's part, by
71
+ * partKey. Only direct dependents count — a part further down the chain is still held by its own
72
+ * prerequisite. [] when the title has no series part, or when another planned entry still carries
73
+ * the same part key, because the dependents are then still held. */
74
+ export function plansReleasedBy(planned, cancelledTitle) {
75
+ const own = seriesPart(cancelledTitle);
76
+ if (own === null)
77
+ return [];
78
+ const key = partKey(own.series, own.part);
79
+ if (plannedPartKeys(planned).has(key))
80
+ return [];
81
+ const released = [];
82
+ planned.forEach((entry, i) => {
83
+ if (requiredParts(entry.title).some((ref) => partKey(ref.series, ref.part) === key)) {
84
+ released.push({ position: i + 1, title: stripEntryStamp(entry.title) });
85
+ }
86
+ });
87
+ return released;
88
+ }
89
+ /** The note a plan cancel adds for plansReleasedBy's result: "" for none, else the positions and
90
+ * titles of the parts that no longer wait, and the command that cancels one. */
91
+ export function releasedPartsNote(parts) {
92
+ if (parts.length === 0)
93
+ return "";
94
+ const tail = " — cancel what cannot stand alone with `tumwater plan --cancel <n>`";
95
+ const titles = parts.map((p) => p.title).join("; ");
96
+ if (parts.length === 1)
97
+ return `plan #${parts[0].position} required it and no longer waits on it: ${titles}${tail}`;
98
+ return `plans ${parts.map((p) => `#${p.position}`).join(", ")} required it and no longer wait on them: ${titles}${tail}`;
99
+ }
100
+ /** The doctor's count of a refusal's stranded dependents: the ascending indexes into `planned` of
101
+ * the entries that require `planned[index]`'s own part, directly or through a chain of parts.
102
+ * Matching is by partKey, as entryHoldAgainst matches. [] when that entry has no series part. A
103
+ * dependency cycle ends the walk, and `index` itself is never listed. */
104
+ export function plansWaitingOn(planned, index) {
105
+ const root = planned[index];
106
+ const own = root === undefined ? null : seriesPart(root.title);
107
+ if (own === null)
108
+ return [];
109
+ const dependents = new Map();
110
+ planned.forEach((entry, i) => {
111
+ if (seriesPart(entry.title) === null)
112
+ return;
113
+ for (const ref of requiredParts(entry.title)) {
114
+ const key = partKey(ref.series, ref.part);
115
+ dependents.set(key, [...(dependents.get(key) ?? []), i]);
116
+ }
117
+ });
118
+ const seen = new Set([index]);
119
+ const queue = [partKey(own.series, own.part)];
120
+ for (let key = queue.shift(); key !== undefined; key = queue.shift()) {
121
+ for (const i of dependents.get(key) ?? []) {
122
+ if (seen.has(i))
123
+ continue;
124
+ seen.add(i);
125
+ const part = seriesPart(planned[i].title);
126
+ queue.push(partKey(part.series, part.part));
127
+ }
128
+ }
129
+ seen.delete(index);
130
+ return [...seen].sort((a, b) => a - b);
131
+ }
132
+ /** The prerequisite parts a plan heading's trailing parenthetical (or, when the heading wraps
133
+ * and the parenthetical stays open past the line, its open parenthetical) names through the clause
134
+ * `requires <ref>((, | and )<ref>)* landed`, with each range (e.g. `parts 1/5–3/5`) expanded.
135
+ * Returns [] when the heading has no such clause or the clause does not parse — an agent judges
136
+ * an unparseable clause, exactly as it does today. Only the heading is read, never the body. */
137
+ export function requiredParts(title) {
138
+ const clause = requiresClause(title);
139
+ if (clause === null)
140
+ return [];
141
+ const ownSeries = seriesPart(title)?.series ?? null;
142
+ const refs = [];
143
+ for (const chunk of clause.split(/\s*,\s*|\s+and\s+/i))
144
+ refs.push(...parseRef(chunk, ownSeries));
145
+ return refs;
146
+ }
147
+ /** The ref text a heading's `requires <refs> landed` clause captures — read from the trailing
148
+ * parenthetical, or the open one of a wrapped heading — or null when the heading has no such
149
+ * clause. requiredParts parses it; planDependencyProblems uses it to spot a clause that parses to
150
+ * nothing. */
151
+ function requiresClause(title) {
152
+ let meta = trailingParenthetical(title);
153
+ if (meta === "")
154
+ meta = openParenthetical(title);
155
+ if (meta === "")
156
+ return null;
157
+ const m = /requires\s+(.+?)\s+landed\b/i.exec(meta); // vocabulary-ok: parses the requires … landed heading syntax
158
+ return m === null ? null : m[1];
159
+ }
160
+ /** A dependency verb followed by a series part ref in a heading's parenthetical — the wording the
161
+ * claim allocator ignores (BUGS.md 2026-10-14: a part headed "blocked by part 1/2 above" was handed
162
+ * out six times while part 1/2 was in flight). planDependencyProblems reports each such ref. */
163
+ const MISWORDED_DEPENDENCY_RE = /\b(?:blocked by|blocked on|after|follows|depends on|waits on|waits for|once)\s+(?:the\s+)?(parts?\s+[0-9]+[a-z]?\/[0-9]+)/gi;
164
+ /** The mistakes in the planned entries' `requires … landed` clauses that the claim allocator
165
+ * cannot see: `dangling` — a ref naming a part no planned or done heading carries (its hold is
166
+ * void), `unparsed` — titles whose clause yields no refs, and `cycle` — the first dependency cycle
167
+ * among planned `Series, part i/n:` entries as `Series i/n` names in walk order, first name
168
+ * repeated last (a self-reference is a one-hop cycle), or null — and `misworded`: a series part
169
+ * whose heading parenthetical names a still-planned part through a dependency verb ("blocked by
170
+ * part 1/2") with no requires clause holding that part, so the allocator hands it out now.
171
+ * Refused and needs-review entries count like the rest, since their clauses still decide when
172
+ * they unblock. */
173
+ export function planDependencyProblems(planned, doneTitles) {
174
+ const known = plannedPartKeys(planned);
175
+ for (const t of doneTitles) {
176
+ const part = seriesPart(t);
177
+ if (part !== null)
178
+ known.add(partKey(part.series, part.part));
179
+ }
180
+ const dangling = [];
181
+ const unparsed = [];
182
+ const misworded = [];
183
+ const plannedKeys = plannedPartKeys(planned);
184
+ const nodes = new Map();
185
+ for (const entry of planned) {
186
+ const refs = requiredParts(entry.title);
187
+ if (refs.length === 0 && requiresClause(entry.title) !== null)
188
+ unparsed.push(entry.title);
189
+ for (const ref of refs) {
190
+ if (!known.has(partKey(ref.series, ref.part)))
191
+ dangling.push({ title: entry.title, ref: `${ref.series} ${ref.part}/${ref.of}` });
192
+ }
193
+ const own = seriesPart(entry.title);
194
+ if (own === null)
195
+ continue;
196
+ let meta = trailingParenthetical(entry.title);
197
+ if (meta === "")
198
+ meta = openParenthetical(entry.title);
199
+ const held = new Set(refs.map((ref) => partKey(ref.series, ref.part)));
200
+ for (const m of meta.matchAll(MISWORDED_DEPENDENCY_RE)) {
201
+ for (const ref of parseRef(m[1], own.series)) {
202
+ const key = partKey(ref.series, ref.part);
203
+ if (!plannedKeys.has(key) || held.has(key))
204
+ continue;
205
+ const name = `${ref.series} ${ref.part}/${ref.of}`;
206
+ if (!misworded.some((w) => w.title === entry.title && w.ref === name))
207
+ misworded.push({ title: entry.title, ref: name });
208
+ }
209
+ }
210
+ nodes.set(partKey(own.series, own.part), {
211
+ name: `${own.series} ${own.part}/${own.of}`,
212
+ deps: refs.map((ref) => partKey(ref.series, ref.part)),
213
+ });
214
+ }
215
+ const state = new Map();
216
+ const stack = [];
217
+ const visit = (key) => {
218
+ state.set(key, "open");
219
+ stack.push(key);
220
+ for (const dep of nodes.get(key).deps) {
221
+ if (!nodes.has(dep))
222
+ continue;
223
+ const seen = state.get(dep);
224
+ if (seen === "open") {
225
+ return [...stack.slice(stack.indexOf(dep)), dep].map((k) => nodes.get(k).name);
226
+ }
227
+ if (seen === undefined) {
228
+ const found = visit(dep);
229
+ if (found !== null)
230
+ return found;
231
+ }
232
+ }
233
+ stack.pop();
234
+ state.set(key, "done");
235
+ return null;
236
+ };
237
+ let cycle = null;
238
+ for (const key of nodes.keys()) {
239
+ if (state.has(key))
240
+ continue;
241
+ cycle = visit(key);
242
+ if (cycle !== null)
243
+ break;
244
+ }
245
+ return { cycle, dangling, unparsed, misworded };
246
+ }
247
+ /** The prerequisite refs of `title`'s `requires … landed` clause when none of them is still in
248
+ * `plannedParts` (built by plannedPartKeys), else []. A heading with no parseable clause is never
249
+ * reported, so only a refusal whose named prerequisites have all left `## Planned` reads as
250
+ * possibly stale. */
251
+ export function prerequisitesMerged(title, plannedParts) {
252
+ const refs = requiredParts(title);
253
+ return refs.some((ref) => plannedParts.has(partKey(ref.series, ref.part))) ? [] : refs;
254
+ }
@@ -0,0 +1,117 @@
1
+ /** The hold-lifting and reordering writes of `tumwater bug|plan --release <n>` and `--top <n>`:
2
+ * lift an entry's Refused, Waiting-for-the-next-failure, or held-for-operator note, or move
3
+ * it to the head of its open
4
+ * section. Each comes as an exiting writer (the CLI) and a non-exiting `try…` core that refuses as
5
+ * data (the TUI and dashboard); entry lookup and the section's file target are
6
+ * backlog-write.ts's. */
7
+ import path from "node:path";
8
+ import { backlogTarget, findEntry, readEntryLines } from "./backlog-write.js";
9
+ import { withoutHeldForOperatorNotes, withoutRefusalNotes, withoutWaitingNotes } from "./backlog-eligibility.js";
10
+ import { readTextIfExists, writeTextAtomic } from "../files/files.js";
11
+ import { fail } from "../cli/cli-output.js";
12
+ import { trimTrailingBlankLines } from "../text/text.js";
13
+ import { withPrimaryWorktreeLock } from "../concurrency/lock.js";
14
+ /** Drop the Refused note from `entry`'s slice of `lines`, in place; an entry with none loses its
15
+ * Waiting-for-the-next-failure note instead, and one with neither loses its held-for-operator
16
+ * Needs-review/Needs-replan note (the `(held N)` count at/above threshold, plus its
17
+ * **Previously held N** line). False (and `lines` untouched) when the entry carries none of the
18
+ * three, shared by releaseEntry and tryReleaseEntry so they cannot drift. */
19
+ function liftRefusal(lines, entry) {
20
+ const slice = lines.slice(entry.start - 1, entry.end);
21
+ let kept = withoutRefusalNotes(slice);
22
+ if (kept.length === slice.length)
23
+ kept = withoutWaitingNotes(slice);
24
+ if (kept.length === slice.length)
25
+ kept = withoutHeldForOperatorNotes(slice);
26
+ if (kept.length === slice.length)
27
+ return false;
28
+ lines.splice(entry.start - 1, slice.length, ...kept);
29
+ return true;
30
+ }
31
+ /** The non-exiting release the dashboard calls: releaseEntry's splice with tryCancelEntry's
32
+ * title check, all decided inside the lock. */
33
+ export function tryReleaseEntry(root, kind, n, expectedTitle) {
34
+ const { fileName, sectionTitle } = backlogTarget(kind);
35
+ return withPrimaryWorktreeLock(root, () => {
36
+ const file = path.join(root, fileName);
37
+ const md = readTextIfExists(file) ?? "";
38
+ const entry = findEntry(md, sectionTitle, n);
39
+ if (entry === undefined)
40
+ return { status: "gone" };
41
+ if (entry.title !== expectedTitle)
42
+ return { status: "moved", title: entry.title };
43
+ const lines = md.split("\n");
44
+ if (!liftRefusal(lines, entry))
45
+ return { status: "not_refused", title: entry.title };
46
+ writeTextAtomic(file, lines.join("\n"));
47
+ return { status: "released", file: fileName, title: entry.title };
48
+ });
49
+ }
50
+ /** Lift the Refused, Waiting for the next failure, or held-for-operator note from the
51
+ * `n`th entry (1-based, as `--list` numbers them) of the kind's open section and write the
52
+ * file, under the same lock the cancel and edit writers hold.
53
+ * An out-of-range position fails with locateEntry's wording; an entry with neither note
54
+ * fails too, writing nothing. */
55
+ export function releaseEntry(root, kind, n) {
56
+ const { fileName, sectionTitle } = backlogTarget(kind);
57
+ return withPrimaryWorktreeLock(root, () => {
58
+ const { file, lines, entry } = readEntryLines(root, fileName, sectionTitle, n);
59
+ if (!liftRefusal(lines, entry))
60
+ fail(`${kind} #${n} "${entry.title}" carries no Refused, Waiting for the next failure, or held-for-operator note`); // vocabulary-ok: the held-for-operator note's release wording
61
+ writeTextAtomic(file, lines.join("\n"));
62
+ return { file: fileName, title: entry.title };
63
+ });
64
+ }
65
+ /** Move the `n`th entry (1-based, as `--list` numbers them) of the kind's open section to the
66
+ * head of that section and write the file, under the same lock as the other writers. Position
67
+ * 1 fails ("is already first") and an out-of-range one fails with locateEntry's wording, both
68
+ * writing nothing. The entry's lines move byte for byte; only the blank lines around the cut
69
+ * and the splice are normalized, and an entry that was the file's last content leaves the file
70
+ * ending as it did before the move (a trailing newline iff it had one). */
71
+ export function moveEntryToTop(root, kind, n) {
72
+ const { fileName, sectionTitle } = backlogTarget(kind);
73
+ return withPrimaryWorktreeLock(root, () => {
74
+ const { file, lines, entry } = readEntryLines(root, fileName, sectionTitle, n);
75
+ if (n === 1)
76
+ fail(`${kind} #1 "${entry.title}" is already first`);
77
+ spliceEntryToTop(lines, sectionTitle, entry);
78
+ writeTextAtomic(file, lines.join("\n"));
79
+ return { file: fileName, title: entry.title };
80
+ });
81
+ }
82
+ /** Cut `entry` out of `lines` and put it at the head of the section, in place; shared by
83
+ * moveEntryToTop and tryMoveEntryToTop so their bytes cannot drift. */
84
+ function spliceEntryToTop(lines, sectionTitle, entry) {
85
+ const first = findEntry(lines.join("\n"), sectionTitle, 1);
86
+ const endsWithNewline = lines[lines.length - 1] === "";
87
+ const atEof = lines.slice(entry.end).every((l) => l.trim() === "");
88
+ const cut = lines.slice(entry.start - 1, entry.end);
89
+ trimTrailingBlankLines(cut);
90
+ lines.splice(entry.start - 1, entry.end - entry.start + 1);
91
+ if (atEof) {
92
+ trimTrailingBlankLines(lines);
93
+ if (endsWithNewline)
94
+ lines.push("");
95
+ }
96
+ lines.splice(first.start - 1, 0, ...cut, "");
97
+ }
98
+ /** The non-exiting reorder the dashboard calls: moveEntryToTop's splice with tryReleaseEntry's
99
+ * title check, all decided inside the lock. */
100
+ export function tryMoveEntryToTop(root, kind, n, expectedTitle) {
101
+ const { fileName, sectionTitle } = backlogTarget(kind);
102
+ return withPrimaryWorktreeLock(root, () => {
103
+ const file = path.join(root, fileName);
104
+ const md = readTextIfExists(file) ?? "";
105
+ const entry = findEntry(md, sectionTitle, n);
106
+ if (entry === undefined)
107
+ return { status: "gone" };
108
+ if (entry.title !== expectedTitle)
109
+ return { status: "moved", title: entry.title };
110
+ if (n === 1)
111
+ return { status: "already_first", title: entry.title };
112
+ const lines = md.split("\n");
113
+ spliceEntryToTop(lines, sectionTitle, entry);
114
+ writeTextAtomic(file, lines.join("\n"));
115
+ return { status: "moved_to_top", file: fileName, title: entry.title };
116
+ });
117
+ }
@@ -1,3 +1,4 @@
1
+ import { entryMatches } from "./backlog.js";
1
2
  import { groupBlockedSeries, partsLabel } from "./backlog-structure.js";
2
3
  import { holdMark, inProgressMark } from "./backlog-eligibility.js";
3
4
  /** One `## ` section: its entries verbatim, or the single `_(none)_` placeholder line. When
@@ -77,3 +78,29 @@ export function readyBacklog(view) {
77
78
  : {}),
78
79
  };
79
80
  }
81
+ /** The view with only the entries whose heading or body contains `pattern` (case-insensitively) —
82
+ * the active-entry twin of the finished-entry `--grep`, through the same one matcher
83
+ * (entryMatches, backlog.ts) the finished-entry lists use: keep plan `i`, bug `i` and question
84
+ * `i` only when entryMatches(entry, pattern), and filter `holds.plans`/`holds.bugs` and
85
+ * `inProgress.plans`/`inProgress.bugs` by the same kept indexes so the parallel arrays stay
86
+ * aligned (the `pick` shape readyBacklog reuses). Questions carry no holds and filter by
87
+ * entryMatches alone. A view without `holds`/`inProgress` only filters the entry arrays. */
88
+ export function grepBacklog(view, pattern) {
89
+ const keep = (xs) => Array.from({ length: xs.length }, (_, i) => i).filter((i) => entryMatches(xs[i], pattern));
90
+ const plan = keep(view.plans);
91
+ const bug = keep(view.bugs);
92
+ const question = keep(view.questions);
93
+ const pick = (xs, idx) => idx.map((i) => xs?.[i]);
94
+ return {
95
+ ...view,
96
+ plans: pick(view.plans, plan),
97
+ bugs: pick(view.bugs, bug),
98
+ questions: pick(view.questions, question),
99
+ ...(view.holds
100
+ ? { holds: { plans: pick(view.holds.plans, plan), bugs: pick(view.holds.bugs, bug) } }
101
+ : {}),
102
+ ...(view.inProgress
103
+ ? { inProgress: { plans: pick(view.inProgress.plans, plan), bugs: pick(view.inProgress.bugs, bug) } }
104
+ : {}),
105
+ };
106
+ }