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,17 +1,31 @@
1
- // vocabulary: plain
2
1
  import path from "node:path";
3
2
  import { increment } from "../collections.js";
4
3
  import { readTextOrNull } from "../files/files.js";
5
4
  import { cachedTextByStat } from "../files/stat-cache.js";
6
5
  import { BACKLOG_FILES, FINISHED_ARCHIVES, fenceTracker, headingMetadata, parseEntryDetails, sectionBodyLines, fenceAwareHeadingLines, headingTitle, } from "./backlog-md.js";
7
6
  import { changeBaseRev, fileContentAt } from "../git/git.js";
8
- import { allLiveClaims } from "../loop/loop-state.js";
7
+ import { plural } from "../text/phrases.js";
9
8
  import { collapseWhitespace, truncate } from "../text/text.js";
10
- import { entryHoldAgainst, holdMark, plannedPartKeys, seriesPart, } from "./backlog-eligibility.js";
11
- import { plannedPlanEntries } from "./backlog.js";
9
+ import { seriesPart } from "./backlog-parts.js";
12
10
  /** The archive files of PLANS.md and BUGS.md (FINISHED_ARCHIVES). */
13
11
  const PLANS_ARCHIVE = FINISHED_ARCHIVES[0].archive;
14
12
  const BUGS_ARCHIVE = FINISHED_ARCHIVES[1].archive;
13
+ /** The `## ` section titles each backlog file is allowed to carry, keyed by file name. A `## `
14
+ * title outside its file's list is an unknown heading — quoted in no reader's section, so every
15
+ * entry under it would be invisible. backlogStructureReason treats known sections as load-bearing
16
+ * (dropping one is rejected) but unknown ones as mistakes: ADDING one stays a legitimate project
17
+ * extension (this repo grew `## Verified`; a doc-only stack can grow `## Notes`), while DROPPING
18
+ * one is allowed — demoting a misplaced `## ` heading to `### ` under a known section is how such
19
+ * a heading is repaired (BUGS.md 2026-10-23). BUGS.md's Verified section is a real section this
20
+ * repo grew beyond its template; the archive and questions files keep the one section their
21
+ * template seeds. */
22
+ const KNOWN_SECTIONS = {
23
+ "PLANS.md": ["Planned", "Done"],
24
+ "PLANS-DONE.md": ["Done"],
25
+ "BUGS.md": ["Open", "Fixed", "Verified"],
26
+ "BUGS-FIXED.md": ["Fixed"],
27
+ "QUESTIONS.md": ["Open", "Answered"],
28
+ };
15
29
  /** The stamp verbs a backlog heading's trailing `(planned 2026-09-29)`-style parenthetical
16
30
  * opens with — the same list the backlog writers stamp and the dashboards parse. One home
17
31
  * for the verb list: the TUI's team alerts strip the suffix (stripEntryStamp) and the
@@ -24,7 +38,7 @@ export const ENTRY_STAMP_META_SOURCE = String.raw `\s*\(((?:planned|reported|fou
24
38
  * (case-insensitive, note captured), read through stripEntryStamp below. */
25
39
  const ENTRY_STAMP_META_RE = new RegExp(ENTRY_STAMP_META_SOURCE, "i");
26
40
  /** A heading or title with its trailing `(planned 2026-09-29)`-style stamp suffix removed,
27
- * whitespace otherwise untouched — the shared span of this module's indexTitle, eligibility's
41
+ * whitespace otherwise untouched — the shared span of backlog-index's indexTitle, eligibility's
28
42
  * entryKey and seriesPart, and the TUI team alerts, which all read a heading stamp-free. */
29
43
  export function stripEntryStamp(title) {
30
44
  return title.replace(ENTRY_STAMP_META_RE, "");
@@ -68,7 +82,7 @@ const DONE_STAMP = /[—,(;]\s*done \d{4}-\d{2}-\d{2}/;
68
82
  * but no `done YYYY-MM-DD` — written as plans, never stamped done, and invisible to every
69
83
  * Planned reader (`plannedPlanEntries` reads only `## Planned`); and (b) entries under
70
84
  * `## Planned` whose heading already carries `done YYYY-MM-DD` — finished and never moved,
71
- * so the feature loop may implement them again. Fence-aware through backlog-md.ts's shared
85
+ * so the feature role may implement them again. Fence-aware through backlog-md.ts's shared
72
86
  * machinery: a `### `/`## ` line inside a fenced code block is quoted content, not structure,
73
87
  * and heading dates are matched against the same joined metadata entryDates matches against.
74
88
  * BUGS.md and QUESTIONS.md are deliberately out of scope — their Fixed-section headings do
@@ -144,6 +158,36 @@ function sectionTitleCounts(md) {
144
158
  export function duplicateHeadings(md) {
145
159
  return [...sectionTitleCounts(md)].filter(([, n]) => n > 1).map(([title]) => title);
146
160
  }
161
+ /** The `### ` headings `head` adds that `base` already carries, as extra copies: for each
162
+ * heading (whitespace-collapsed, every `## ` section, fence-aware) present in `base` and more
163
+ * often in `head`, one entry per added copy, in the head's first-appearance order. The archive
164
+ * files' re-pasted-history signal: on 2026-10-19 a conflict hand-back re-pasted 383 + 310 archived
165
+ * entries, and the commit-time sweep prepended them to PLANS-DONE.md and BUGS-FIXED.md again. A
166
+ * heading the base already duplicates never counts unless the head adds yet another copy. */
167
+ export function readdedHeadings(base, head) {
168
+ const count = (md) => {
169
+ const out = new Map();
170
+ for (const line of fenceAwareHeadingLines(md, "### ")) {
171
+ const title = headingTitle(line, "### ");
172
+ const key = collapseWhitespace(title);
173
+ const seen = out.get(key);
174
+ if (seen)
175
+ seen.n++;
176
+ else
177
+ out.set(key, { n: 1, title });
178
+ }
179
+ return out;
180
+ };
181
+ const baseCounts = count(base);
182
+ const readded = [];
183
+ for (const [key, { n, title }] of count(head)) {
184
+ const had = baseCounts.get(key)?.n ?? 0;
185
+ if (had > 0 && n > had)
186
+ for (let i = had; i < n; i++)
187
+ readded.push(title);
188
+ }
189
+ return readded;
190
+ }
147
191
  /** The `## Fixed` entry headings whose body holds more than one `**Symptom:**` or
148
192
  * `**Fix:**` block — the shape a record with no `### ` heading leaves when parseEntryDetails
149
193
  * absorbs it into the entry above (an entry opens only at a `### ` line, so the headingless
@@ -177,9 +221,13 @@ export function doubleBlockFixedEntries(md) {
177
221
  * against, so a stacked batch is judged change by change — and rejects when (a) the head carries
178
222
  * MORE of a title than the base carried (a duplicate `## Done` added, or a second one where the
179
223
  * base had none), or (b) a title present on the base is gone from the head (a whole section
180
- * dropped). The rule is deliberately about heading sets, not section names: a project whose BUGS.md
181
- * adds `## Verified` (this repo's does) or a fresh repo seeded from src/init/init.ts's templates
182
- * passes unchanged. A base that already carries a duplicate never blocks unrelated edits — rule (a)
224
+ * dropped). Dropping is judged against the file's KNOWN_SECTIONS — PLANS.md Planned and Done,
225
+ * its archive Done, BUGS.md Open, Fixed and Verified, its archive Fixed, QUESTIONS.md Open and
226
+ * Answered: an unknown `## ` heading (a misplaced one) is free to be dropped — demoting it to
227
+ * `### ` under a known section is the repair (BUGS.md 2026-10-23) — while dropping a known
228
+ * section stays rejected. ADDING a `## ` heading is still a legitimate project extension (this
229
+ * repo's `## Verified`, a doc-only stack's `## Notes`), so the rule stays about heading sets, not
230
+ * section names. A base that already carries a duplicate never blocks unrelated edits — rule (a)
183
231
  * fires only when the head's count EXCEEDS the base's, so a change that removes a duplicate always
184
232
  * passes. For PLANS.md there is a third rule (part 4/4): a head that ADDS a `### ` entry directly
185
233
  * under `## Done` whose joined heading metadata carries `(planned YYYY-MM-DD` but no `done
@@ -187,11 +235,14 @@ export function doubleBlockFixedEntries(md) {
187
235
  * the wrong section — rejected with a reason naming the entry and the one-line fix. Judged against
188
236
  * the merge-base (the same base the heading rules use), so a Planned → Done move of an entry the
189
237
  * base already had passes, a stacked batch is measured change by change, and a pre-existing
190
- * stranded entry (whose key the base has) never blocks unrelated merges — the clean loop's repair
238
+ * stranded entry (whose key the base has) never blocks unrelated merges — the clean role's repair
191
239
  * (part 3/4) owns those. BUGS.md has one more: a head `## Fixed` entry that holds more than one
192
240
  * `**Symptom:**` or `**Fix:**` block (doubleBlockFixedEntries) absorbed a headingless record into
193
241
  * its body; a base body that already had the shape is never re-flagged, so only a newly merged
194
- * record is rejected (BUGS.md 2026-10-08). Detection is deterministic markdown reading — no pi — so
242
+ * record is rejected (BUGS.md 2026-10-08). The archive files (PLANS-DONE.md, BUGS-FIXED.md) have
243
+ * one more: a head that adds a copy of a `### ` heading the base archive already carries
244
+ * (readdedHeadings) is pasting finished history back, rejected with the count and the first
245
+ * heading. Detection is deterministic markdown reading — no pi — so
195
246
  * both the review gate (exempt and code diffs alike) and the in-lock merge re-check can afford it
196
247
  * on every merge (plans: "Backlog structure check", part 2/4). */
197
248
  export async function backlogStructureReason(wt, mainBranch, files) {
@@ -209,10 +260,25 @@ export async function backlogStructureReason(wt, mainBranch, files) {
209
260
  if (count > 1 && count > (baseCounts.get(title) ?? 0))
210
261
  return (`${file} adds another "## ${title}" heading — readers take the first section of a ` +
211
262
  `name, so anything under the duplicate is invisible; keep one "## ${title}" per file`);
263
+ // A `## ` title that is not one of this file's known sections (KNOWN_SECTIONS) is not
264
+ // load-bearing: ADDING one is a legitimate project extension (this repo grew `## Verified`,
265
+ // a doc-only stack can grow `## Notes`), so a new one passes — but DROPPING one is the repair
266
+ // for a misplaced heading, so rule (b) guards only known sections and leaves an unknown one
267
+ // free to be demoted to `### ` under a known section (BUGS.md 2026-10-23).
268
+ const known = KNOWN_SECTIONS[file] ?? [];
212
269
  for (const title of baseCounts.keys())
213
- if (!headCounts.has(title))
270
+ if (!headCounts.has(title) && known.includes(title))
214
271
  return (`${file} drops the "## ${title}" section the base had — entries filed under it would ` +
215
272
  `be invisible to every section reader; restore "## ${title}"`);
273
+ // An archive only grows by new headings: a copy of one the base archive already carries is
274
+ // finished history pasted back (readdedHeadings), never a new finished entry.
275
+ if (file === PLANS_ARCHIVE || file === BUGS_ARCHIVE) {
276
+ const readded = readdedHeadings(base, head);
277
+ if (readded.length > 0)
278
+ return (`${file} adds ${plural(readded.length, '"### " heading', '"### " headings')} it already carries ` +
279
+ `(first: "${truncate(readded[0], 160)}") — the archive holds each finished entry once, so the ` +
280
+ `copies duplicate its history; remove them, and edit an archived entry in place instead`);
281
+ }
216
282
  // Part 4/4's rule, PLANS.md and its archive (see this function's doc comment): a NEW plan
217
283
  // filed directly under ## Done with no done date. strandedPlanEntries already does the
218
284
  // fence-aware, joined-metadata reading the first two conditions need; the base's key set —
@@ -225,9 +291,18 @@ export async function backlogStructureReason(wt, mainBranch, files) {
225
291
  ]);
226
292
  const newcomer = strandedPlanEntries(head).find((e) => e.section === "Done" && !baseKeys.has(planHeadingKey(e.title)));
227
293
  if (newcomer)
228
- return (`${file} files "${newcomer.title}" directly under "## Done" with no done date — a plan ` +
229
- `written into the done section is invisible to every Planned reader; file it under ` +
230
- `"## Planned"${file === "PLANS.md" ? "" : " in PLANS.md"} instead`);
294
+ // The fault and its fix differ by file: in PLANS.md a NEW plan (the base never carried
295
+ // it in any section) was pasted under ## Done unfinished and belongs under ## Planned;
296
+ // in the archive the same heading is a finished plan the commit-time sweep already moved
297
+ // here, whose only fault is a missing `done YYYY-MM-DD` token — telling its author to
298
+ // "file it under ## Planned" is the opposite of the fix and wastes a revision.
299
+ return file === PLANS_ARCHIVE
300
+ ? `${file} files "${newcomer.title}" directly under "## Done" with no done date — the ` +
301
+ `entry is finished and archived, so the only fix is to add \`done YYYY-MM-DD\` to its ` +
302
+ `"### " heading; do not move it back under "## Planned"`
303
+ : `${file} files "${newcomer.title}" directly under "## Done" with no done date — a plan ` +
304
+ `written into the done section is invisible to every Planned reader; file it under ` +
305
+ `"## Planned" instead`;
231
306
  }
232
307
  // A new headingless record cannot hide in a Fixed entry's body: reject a head whose
233
308
  // multi-block entries the base did not carry (an existing one never blocks unrelated
@@ -249,7 +324,7 @@ export async function backlogStructureReason(wt, mainBranch, files) {
249
324
  const strandedPlanCache = new Map();
250
325
  /** `strandedPlanEntries` for the primary checkout's PLANS.md and PLANS-DONE.md (where finished
251
326
  * plans are archived, plans/backlog-archive.md), each entry tagged with its file, stat-cached per
252
- * file path (stat-cache.cachedTextByStat): the clean loop asks for it every tick, `tumwater role`
327
+ * file path (stat-cache.cachedTextByStat): the clean role asks for it every run, `tumwater role`
253
328
  * previews the same block and doctor reports it, and the archive grows without bound, so an
254
329
  * unchanged file costs one stat instead of a full read and section walk. A missing or
255
330
  * unreadable file reads as no entries. The result is copied out, so each caller owns its
@@ -260,8 +335,8 @@ export function strandedPlanEntriesFor(root) {
260
335
  return (cachedTextByStat(strandedPlanCache, full, full, (md) => strandedPlanEntries(md).map((e) => ({ ...e, file })), (entries) => entries.slice()) ?? []);
261
336
  });
262
337
  }
263
- /** The clean loop's `<backlog-structure>` prompt block for the primary checkout at `root`
264
- * (the same root the telemetry digest and qa coverage blocks read): a rendered block listing
338
+ /** The clean role's `<backlog-structure>` prompt block for the primary checkout at `root`
339
+ * (the same root the selfcheck digest and qa coverage blocks read): a rendered block listing
265
340
  * each stranded heading with its current section, or undefined when PLANS.md is missing,
266
341
  * unreadable, or clean — an unreadable or clean file gives no block, so the prompt is
267
342
  * unchanged in the common case. The stranded entries are stat-cached (strandedPlanEntriesFor),
@@ -285,7 +360,7 @@ ${listed}
285
360
  * order: the fence-aware walk sectionLines and parseEntryDetails share, carrying the line
286
361
  * numbers neither returns. An entry starts at a `### ` line and ends at the line before the
287
362
  * next `### ` or `## ` line (or EOF) — the same boundary parseEntryDetails closes bodies on —
288
- * so a loop reading lines `start`..`end` sees exactly the entry's heading and body. A `## ` or
363
+ * so a role reading lines `start`..`end` sees exactly the entry's heading and body. A `## ` or
289
364
  * `### ` line inside a fenced code block is body content, never a boundary, through
290
365
  * backlog-md.ts's shared fenceTracker. */
291
366
  export function actionableEntryRanges(md, sectionTitle) {
@@ -322,7 +397,7 @@ export function actionableEntryRanges(md, sectionTitle) {
322
397
  /** Per-file cache of a section's actionable ranges (actionableEntryRangesFor). */
323
398
  const actionableRangesCache = new Map();
324
399
  /** `actionableEntryRanges` for one backlog file, stat-cached per (file path, section title):
325
- * the per-poll and per-tick readers (eligibility's eligibleEntries, claims' listedKeys) ask for
400
+ * the per-poll and per-run readers (eligibility's eligibleEntries, claims' listedKeys) ask for
326
401
  * the same section of the same file every couple of seconds, and BUGS.md's closed history —
327
402
  * which the whole-file walk reads — grows without bound, so an unchanged file costs one stat
328
403
  * instead of a full read, split, and section walk. Keyed by file path so distinct roots never
@@ -332,43 +407,6 @@ export function actionableEntryRangesFor(root, file, sectionTitle) {
332
407
  const full = path.join(root, file);
333
408
  return (cachedTextByStat(actionableRangesCache, `${full}\u0000${sectionTitle}`, full, (md) => actionableEntryRanges(md, sectionTitle), (ranges) => ranges.slice()) ?? []);
334
409
  }
335
- /** Max characters an index title keeps before a trailing ellipsis, so one very long BUGS.md
336
- * heading cannot dominate the block. */
337
- const INDEX_TITLE_MAX = 160;
338
- /** The index title for a `### ` heading: its stamp suffix (`(reported … 2026-…)`) removed via
339
- * the shared stripEntryStamp and whitespace collapsed, then clipped to INDEX_TITLE_MAX
340
- * through text.ts's truncate — the one ellipsis rule, so the cut cannot drift from the
341
- * other trimmed labels and never splits a surrogate pair. */
342
- function indexTitle(heading) {
343
- return truncate(collapseWhitespace(stripEntryStamp(heading)), INDEX_TITLE_MAX);
344
- }
345
- /** The files and sections the actionable index covers, in the order loops read them: PLANS.md's
346
- * planned features, BUGS.md's open bugs, QUESTIONS.md's open questions. */
347
- const INDEX_SECTIONS = [
348
- { file: "PLANS.md", section: "Planned" },
349
- { file: "BUGS.md", section: "Open" },
350
- { file: "QUESTIONS.md", section: "Open" },
351
- ];
352
- /** How many closed entries per closed section the index lists. */
353
- const RECENT_CLOSED_MAX = 5;
354
- /** The closed sections whose newest entries the index also lists, each read from the live file
355
- * and then its archive (FINISHED_ARCHIVES, plans/backlog-archive.md); every one keeps its entries
356
- * newest first, so the first entries in file order are the recent ones. */
357
- const CLOSED_SECTIONS = FINISHED_ARCHIVES.map((a) => ({
358
- files: [a.file, a.archive],
359
- section: a.section,
360
- }));
361
- /** Per-file cache of the parsed actionable entries (indexSectionCache). */
362
- const indexSectionCache = new Map();
363
- /** Per-file cache of the newest closed entries' ranges; separate from indexSectionCache, which is
364
- * keyed by the file path alone and PLANS.md would collide. */
365
- const closedSectionCache = new Map();
366
- /** One `IndexRow` as an ordinary index line: range, truncated stamp-free title, hold mark, and
367
- * the in-flight suffix when a live claim names it. The collapsed-series line is built by
368
- * collapseBlockedSeries, returned alongside these. */
369
- function renderIndexRow(row) {
370
- return `- ${row.start}-${row.end}: ${indexTitle(row.title)}${row.mark}${row.claimer ? ` [in flight by ${row.claimer}]` : ""}`;
371
- }
372
410
  /** True when `row` is a candidate for series-collapsing: it is a series part held only by an
373
411
  * unmerged prerequisite (`{ blockedBy }`, not a Refused/needs-review/needs-replan note) and no live
374
412
  * claim names it. Returns the row's series part when it collapses, else null. */
@@ -415,7 +453,7 @@ export function partsLabel(rows) {
415
453
  }
416
454
  /** Renders `rows` to index lines, collapsing a multi-part series' blocked parts to one line per
417
455
  * series (this plan, "Compact backlog index, part 1/1"): a long series' chain of held parts would
418
- * otherwise fill every loop prompt with lines no role can act on. An entry collapses when it is a
456
+ * otherwise fill every role prompt with lines no role can act on. An entry collapses when it is a
419
457
  * series part held only by an unmerged prerequisite and no live claim names it; a series with two
420
458
  * or more such parts renders as one line at the position of its first collapsed part, spanning from
421
459
  * that part's start to the last's end, with the part list (`2/4–4/4`) and a `[blocked: each waits
@@ -423,9 +461,9 @@ export function partsLabel(rows) {
423
461
  * (Refused/needs-review/needs-replan) part and a claimed part keep their ordinary lines, so the
424
462
  * eligibility rules the feature role's charter teaches are unchanged; the collapsed mark keeps the
425
463
  * `[blocked: …]` prefix it already skips. */
426
- /** The grouping half of collapseBlockedSeries, on any rows carrying a title, hold and claimer:
427
- * each series with two or more collapsible blocked parts becomes one `series` group at its first
428
- * part's place; every other row stays a `row` group in order. */
464
+ /** The grouping half of backlog-index's collapseBlockedSeries, on any rows carrying a title,
465
+ * hold and claimer: each series with two or more collapsible blocked parts becomes one `series`
466
+ * group at its first part's place; every other row stays a `row` group in order. */
429
467
  export function groupBlockedSeries(rows) {
430
468
  // Group the collapsible rows by series, keyed on the lowercased series name.
431
469
  const bySeries = new Map();
@@ -461,98 +499,3 @@ export function groupBlockedSeries(rows) {
461
499
  }
462
500
  return groups;
463
501
  }
464
- function collapseBlockedSeries(rows) {
465
- return groupBlockedSeries(rows).map((g) => {
466
- if (g.kind === "row")
467
- return renderIndexRow(g.row);
468
- const first = g.rows[0];
469
- const last = g.rows[g.rows.length - 1];
470
- return (`- ${first.start}-${last.end}: ${indexTitle(g.series)}, ${g.rows.length} blocked parts ` +
471
- `(${partsLabel(g.rows)}) [blocked: each waits on an earlier part]`);
472
- });
473
- }
474
- /** The `<backlog-index>` prompt block: each actionable entry (PLANS.md ## Planned, BUGS.md
475
- * ## Open, QUESTIONS.md ## Open) with its 1-based line range, rendered from the primary
476
- * checkout `root`. Replaces the per-tick `grep -n '^##'` heading map, whose pattern also matched
477
- * every `###` title in BUGS.md's closed history and so re-sent ~108 KB on every turn (BUGS.md
478
- * 2026-10-06). A missing or unreadable file contributes no sections; undefined when no section
479
- * has entries, so an empty backlog leaves the prompt unchanged. The per-file actionable entries
480
- * are stat-cached (indexSectionCache), so an unchanged backlog file is not re-read or re-walked. */
481
- export function renderBacklogIndexBlock(root) {
482
- // The still-planned set every section's holds are judged against. A prerequisite clause rides
483
- // in the entry heading, and the eligibility gate (backlog-eligibility.ts's eligibleEntries)
484
- // and `tumwater backlog`'s holds read it for BUGS.md's Open entries too, so the same set must
485
- // reach this block's Open section as well — otherwise a held bug reads as eligible here while
486
- // the gate skips it. plannedPlanEntries is stat-cached by sectionEntries, so an unchanged
487
- // PLANS.md costs one stat per call.
488
- const planned = plannedPlanEntries(root);
489
- const plannedParts = plannedPartKeys(planned);
490
- // The cross-role in-flight hold (BUGS.md 2026-10-09): every role's prompts share this one
491
- // section-agnostic index, and the role it is assembled for is not available here, so an entry
492
- // claimed by ANY role is marked in flight — the owner's own index marks it too, which is harmless
493
- // (its assignment note is the instruction), and every other role reads it as taken instead of
494
- // re-implementing a change someone's merge is already carrying to main.
495
- const claimed = allLiveClaims(root);
496
- const listed = [];
497
- for (const { file, section } of INDEX_SECTIONS) {
498
- const full = path.join(root, file);
499
- const entries = cachedTextByStat(indexSectionCache, full, full, (md) => {
500
- const ranges = actionableEntryRanges(md, section);
501
- if (ranges.length === 0)
502
- return [];
503
- const lines = md.split(/\r?\n/);
504
- // The body slice is the lines after the heading through the last body line, so a note
505
- // is found whether it sits first or last (the heading is at 1-based line e.start).
506
- return ranges.map((e) => ({
507
- start: e.start,
508
- end: e.end,
509
- title: e.title,
510
- body: lines.slice(e.start, e.end).join("\n"),
511
- }));
512
- }, (v) => v.slice());
513
- if (entries === null || entries.length === 0)
514
- continue;
515
- listed.push(`${file} ## ${section}`);
516
- // Build the rows once per section, then collapse a PLANS.md series' blocked parts (this
517
- // plan) — BUGS.md and QUESTIONS.md sections keep every entry on its own line.
518
- const rows = entries.map((e) => {
519
- const hold = entryHoldAgainst({ title: e.title, body: e.body }, plannedParts);
520
- const key = collapseWhitespace(stripEntryStamp(e.title)).toLowerCase();
521
- return {
522
- start: e.start,
523
- end: e.end,
524
- title: e.title,
525
- hold,
526
- mark: holdMark(hold),
527
- claimer: claimed.get(key),
528
- };
529
- });
530
- const isPlans = file === "PLANS.md" && section === "Planned";
531
- listed.push(...(isPlans ? collapseBlockedSeries(rows) : rows.map(renderIndexRow)));
532
- }
533
- for (const { files, section } of CLOSED_SECTIONS) {
534
- // The newest RECENT_CLOSED_MAX across the live section (normally empty) and the archive.
535
- let room = RECENT_CLOSED_MAX;
536
- for (const file of files) {
537
- if (room <= 0)
538
- break;
539
- const full = path.join(root, file);
540
- const entries = cachedTextByStat(closedSectionCache, full, full, (md) => actionableEntryRanges(md, section).slice(0, RECENT_CLOSED_MAX), (v) => v.slice())?.slice(0, room);
541
- if (entries === undefined || entries.length === 0)
542
- continue;
543
- room -= entries.length;
544
- listed.push(`${file} ## ${section} (newest ${entries.length})`);
545
- for (const e of entries)
546
- listed.push(`- ${e.start}-${e.end}: ${indexTitle(e.title)}`);
547
- }
548
- }
549
- if (listed.length === 0)
550
- return undefined;
551
- return `Backlog index — each actionable entry, then the newest closed ones, with the 1-based line range it occupies in its file.
552
- Read the entries you need by those ranges; the archive files (PLANS-DONE.md, BUGS-FIXED.md) grow
553
- with every merge, so search them rather than reading them. Blocked parts of a multi-part series are
554
- shown as one line per series.
555
- <backlog-index>
556
- ${listed.join("\n")}
557
- </backlog-index>`;
558
- }