tumwater 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +74 -108
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +24 -0
  4. package/dist/src/agent/agent-backend.js +2 -2
  5. package/dist/src/agent/backend-traits.js +60 -0
  6. package/dist/src/agent/claude/claude-account.js +61 -8
  7. package/dist/src/agent/claude/claude-args.js +12 -15
  8. package/dist/src/agent/claude/claude-backend.js +51 -41
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +7 -16
  11. package/dist/src/agent/image-input.js +107 -0
  12. package/dist/src/agent/notes-shim.js +41 -0
  13. package/dist/src/agent/opencode/opencode-args.js +61 -0
  14. package/dist/src/agent/opencode/opencode-backend.js +255 -0
  15. package/dist/src/agent/opencode/opencode-catalog.js +184 -0
  16. package/dist/src/agent/opencode/opencode-config.js +188 -0
  17. package/dist/src/agent/opencode/opencode-env.js +36 -0
  18. package/dist/src/agent/opencode/opencode-session-id.js +37 -0
  19. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  20. package/dist/src/agent/role-notes-write.js +16 -32
  21. package/dist/src/agent/run-agent.js +95 -18
  22. package/dist/src/agent/session-ledger.js +57 -1
  23. package/dist/src/backlog/backlog-archive.js +296 -0
  24. package/dist/src/backlog/backlog-cli.js +573 -0
  25. package/dist/src/backlog/backlog-commit.js +31 -0
  26. package/dist/src/backlog/backlog-eligibility.js +357 -33
  27. package/dist/src/backlog/backlog-md.js +98 -18
  28. package/dist/src/backlog/backlog-render.js +62 -11
  29. package/dist/src/backlog/backlog-structure.js +261 -58
  30. package/dist/src/backlog/backlog-write.js +197 -319
  31. package/dist/src/backlog/backlog.js +113 -16
  32. package/dist/src/backlog/question-answer.js +339 -0
  33. package/dist/src/baseline/main-baseline.js +75 -88
  34. package/dist/src/baseline/main-red.js +38 -30
  35. package/dist/src/brief/brief-edit.js +43 -0
  36. package/dist/src/{brief.js → brief/brief.js} +66 -10
  37. package/dist/src/budget/budget.js +32 -15
  38. package/dist/src/budget/fallback-breaker.js +8 -7
  39. package/dist/src/build/build-check-detect.js +8 -7
  40. package/dist/src/build/build-check-events.js +68 -22
  41. package/dist/src/build/build-check-report.js +22 -23
  42. package/dist/src/build/build-check-scoped.js +51 -62
  43. package/dist/src/build/build-check.js +15 -14
  44. package/dist/src/build/build-info.js +2 -1
  45. package/dist/src/build/build-stage.js +23 -22
  46. package/dist/src/build/dep-install.js +22 -21
  47. package/dist/src/build/host-sleep.js +1 -1
  48. package/dist/src/change/change-data.js +25 -14
  49. package/dist/src/change/change-render.js +11 -10
  50. package/dist/src/cli/brief-command.js +58 -0
  51. package/dist/src/cli/cli-args.js +25 -5
  52. package/dist/src/cli/cli-command-args.js +46 -14
  53. package/dist/src/cli/cli-editor.js +64 -0
  54. package/dist/src/cli/cli-flag-specs.js +54 -16
  55. package/dist/src/cli/cli-marker-commands.js +13 -10
  56. package/dist/src/cli/cli-output.js +9 -0
  57. package/dist/src/cli/cli-query-commands.js +458 -49
  58. package/dist/src/cli/cli-repo-gate.js +1 -1
  59. package/dist/src/cli/cli-run.js +208 -59
  60. package/dist/src/cli/completion.js +207 -0
  61. package/dist/src/cli/config-commands.js +14 -11
  62. package/dist/src/cli/help.js +305 -110
  63. package/dist/src/cli/log-commands.js +127 -21
  64. package/dist/src/cli/notify-command.js +33 -18
  65. package/dist/src/cli/question-commands.js +29 -224
  66. package/dist/src/cli.js +121 -40
  67. package/dist/src/collections.js +66 -16
  68. package/dist/src/concurrency/check-permit.js +16 -17
  69. package/dist/src/concurrency/lock.js +6 -5
  70. package/dist/src/concurrency/provider-lease.js +69 -0
  71. package/dist/src/concurrency/semaphore.js +258 -36
  72. package/dist/src/config/config-aliases.js +106 -0
  73. package/dist/src/config/config-editable-keys.js +3 -3
  74. package/dist/src/config/config-fallbacks.js +379 -0
  75. package/dist/src/config/config-field-checks.js +19 -10
  76. package/dist/src/config/config-keys.js +160 -0
  77. package/dist/src/config/config-live.js +64 -12
  78. package/dist/src/config/config-schema.js +34 -22
  79. package/dist/src/config/config-validation.js +132 -48
  80. package/dist/src/config/config-views.js +119 -337
  81. package/dist/src/config/config-write.js +138 -177
  82. package/dist/src/config/config.js +60 -49
  83. package/dist/src/config/model-selector.js +28 -2
  84. package/dist/src/doctor/doctor-backlog.js +546 -49
  85. package/dist/src/doctor/doctor-checks.js +534 -56
  86. package/dist/src/doctor/doctor-holds.js +193 -53
  87. package/dist/src/doctor/doctor-install.js +30 -0
  88. package/dist/src/doctor/doctor-launch-services.js +2 -2
  89. package/dist/src/doctor/doctor-model-checks.js +549 -157
  90. package/dist/src/doctor/doctor-notify.js +27 -0
  91. package/dist/src/doctor/doctor-orphans.js +10 -9
  92. package/dist/src/doctor/doctor-render.js +36 -9
  93. package/dist/src/doctor/doctor.js +80 -22
  94. package/dist/src/doctor/image-probe.js +244 -0
  95. package/dist/src/doctor/probe.js +64 -0
  96. package/dist/src/events/event-format.js +226 -102
  97. package/dist/src/events/event-labels.js +116 -0
  98. package/dist/src/events/event-legacy.js +140 -0
  99. package/dist/src/events/event-read.js +58 -22
  100. package/dist/src/events/event-window.js +27 -7
  101. package/dist/src/events/events.js +18 -14
  102. package/dist/src/events/notify.js +137 -38
  103. package/dist/src/failure/failure-cluster.js +12 -9
  104. package/dist/src/failure/failure-data.js +96 -48
  105. package/dist/src/failure/failure-render.js +69 -58
  106. package/dist/src/failure/failure-state-change.js +33 -26
  107. package/dist/src/failure/time-spend.js +79 -59
  108. package/dist/src/files/file-queue.js +16 -4
  109. package/dist/src/files/files.js +18 -6
  110. package/dist/src/files/json-object.js +18 -3
  111. package/dist/src/files/tail.js +1 -1
  112. package/dist/src/gates/bootstrap-gates.js +5 -4
  113. package/dist/src/gates/budget-gates.js +250 -34
  114. package/dist/src/gates/disk-gate.js +18 -18
  115. package/dist/src/gates/gate-polls.js +117 -69
  116. package/dist/src/gates/maintenance-quota.js +29 -29
  117. package/dist/src/gates/pause-gates.js +11 -11
  118. package/dist/src/gates/readiness.js +2 -1
  119. package/dist/src/gates/role-cap-gates.js +16 -21
  120. package/dist/src/gates/startup-gate.js +75 -31
  121. package/dist/src/gates/streak-gate.js +18 -16
  122. package/dist/src/git/commit-message.js +12 -5
  123. package/dist/src/git/git-diff.js +16 -16
  124. package/dist/src/git/git-run.js +8 -8
  125. package/dist/src/git/git.js +86 -55
  126. package/dist/src/git/slots-state.js +14 -5
  127. package/dist/src/git/worktree-pool.js +25 -18
  128. package/dist/src/git/worktree-use.js +15 -14
  129. package/dist/src/git/worktree.js +18 -18
  130. package/dist/src/gui/gui-args.js +10 -14
  131. package/dist/src/gui/gui-backlog-commands.js +273 -0
  132. package/dist/src/gui/gui-command.js +1 -1
  133. package/dist/src/gui/gui-config-commands.js +440 -0
  134. package/dist/src/gui/gui-endpoint-commands.js +16 -669
  135. package/dist/src/gui/gui-endpoint-common.js +49 -0
  136. package/dist/src/gui/gui-endpoints.js +116 -56
  137. package/dist/src/gui/gui-operator-commands.js +145 -0
  138. package/dist/src/gui/gui-prompt-commands.js +234 -0
  139. package/dist/src/gui/gui-server.js +28 -8
  140. package/dist/src/gui/gui-team-commands.js +164 -0
  141. package/dist/src/gui/http-body.js +17 -1
  142. package/dist/src/history/history-data.js +194 -98
  143. package/dist/src/history/history-render.js +76 -0
  144. package/dist/src/history/history.js +37 -58
  145. package/dist/src/inbox/inbox-attachments.js +75 -14
  146. package/dist/src/inbox/inbox-cancel.js +7 -5
  147. package/dist/src/inbox/inbox-edit.js +1 -1
  148. package/dist/src/inbox/inbox-submit.js +44 -4
  149. package/dist/src/inbox/inbox-top.js +83 -0
  150. package/dist/src/inbox/inbox.js +8 -2
  151. package/dist/src/inbox/pending-prompt.js +1 -1
  152. package/dist/src/inbox/prompt-commands.js +170 -80
  153. package/dist/src/init/init-templates.js +16 -14
  154. package/dist/src/init/init.js +12 -11
  155. package/dist/src/loop/leftover.js +28 -27
  156. package/dist/src/loop/loop-fallback.js +2 -2
  157. package/dist/src/loop/loop-pi.js +106 -52
  158. package/dist/src/loop/loop-recovery.js +15 -15
  159. package/dist/src/loop/loop-state.js +139 -13
  160. package/dist/src/loop/loop-tick.js +194 -86
  161. package/dist/src/loop/loop.js +72 -36
  162. package/dist/src/loop/model-fallback.js +3 -2
  163. package/dist/src/loop/revision.js +16 -7
  164. package/dist/src/merge/backlog-conflicts.js +155 -0
  165. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
  166. package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
  167. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
  168. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
  169. package/dist/src/merge/merge-drain.js +212 -0
  170. package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
  171. package/dist/src/merge/merge-pipeline.js +99 -0
  172. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  173. package/dist/src/merge/merge-queue-render.js +75 -0
  174. package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
  175. package/dist/src/merge/merge-slot.js +340 -0
  176. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
  177. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
  178. package/dist/src/merge/merge-verify.js +212 -0
  179. package/dist/src/notes-cli.js +97 -0
  180. package/dist/src/operator/operator-commands.js +154 -47
  181. package/dist/src/operator/operator-intent.js +87 -49
  182. package/dist/src/operator/operator-requests.js +52 -52
  183. package/dist/src/operator/retire.js +62 -27
  184. package/dist/src/operator/revert.js +71 -0
  185. package/dist/src/orchestrator/orchestrator-launch.js +74 -14
  186. package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
  187. package/dist/src/orchestrator/orchestrator.js +158 -100
  188. package/dist/src/orchestrator/retention.js +29 -12
  189. package/dist/src/orchestrator/startup-migration-steps.js +51 -0
  190. package/dist/src/orchestrator/startup-migrations.js +40 -0
  191. package/dist/src/paths.js +128 -37
  192. package/dist/src/pi/pi-args.js +26 -3
  193. package/dist/src/pi/pi-backend.js +15 -23
  194. package/dist/src/pi/pi-bin.js +18 -12
  195. package/dist/src/pi/pi-event-line.js +17 -1
  196. package/dist/src/pi/pi-failure.js +87 -0
  197. package/dist/src/pi/pi-models.js +126 -38
  198. package/dist/src/pi/pi-run-result.js +1 -1
  199. package/dist/src/pi/pi-settings.js +86 -0
  200. package/dist/src/pi/pi-stream.js +131 -107
  201. package/dist/src/pi/pi-watchdogs.js +47 -43
  202. package/dist/src/pi-extension/bounded-output.js +20 -5
  203. package/dist/src/pi-extension/context-budget.js +3 -1
  204. package/dist/src/pi-extension/context-shake.js +54 -21
  205. package/dist/src/pi-extension/context-window.js +37 -0
  206. package/dist/src/pi-extension/full-output.js +2 -2
  207. package/dist/src/pi-extension/strip-thinking.js +80 -0
  208. package/dist/src/pi-extension/trim-system-prompt.js +38 -0
  209. package/dist/src/process/launch-services.js +11 -10
  210. package/dist/src/process/process-table.js +1 -1
  211. package/dist/src/process/process.js +7 -7
  212. package/dist/src/process/run-marker.js +29 -6
  213. package/dist/src/process/supervisor.js +47 -25
  214. package/dist/src/prompt/dialect.js +30 -10
  215. package/dist/src/prompt/gate-prompts.js +170 -67
  216. package/dist/src/prompt/principles.js +1 -0
  217. package/dist/src/prompt/prompt-followup.js +35 -20
  218. package/dist/src/prompt/prompt.js +249 -91
  219. package/dist/src/redeploy/redeploy-policy.js +10 -10
  220. package/dist/src/redeploy/redeploy.js +6 -4
  221. package/dist/src/redeploy/redeployer.js +63 -55
  222. package/dist/src/redeploy/self-reload.js +1 -1
  223. package/dist/src/report/report-data.js +264 -255
  224. package/dist/src/report/report-fold.js +353 -0
  225. package/dist/src/report/report-render.js +270 -33
  226. package/dist/src/request-timeouts.js +1 -1
  227. package/dist/src/review/known-flakes.js +34 -14
  228. package/dist/src/review/protected-edits.js +33 -0
  229. package/dist/src/review/review-followup.js +16 -4
  230. package/dist/src/review/review-precheck.js +14 -13
  231. package/dist/src/review/review-verdict.js +14 -0
  232. package/dist/src/review/review.js +81 -39
  233. package/dist/src/review/secret-leaks.js +127 -0
  234. package/dist/src/review/suite-rerun.js +4 -3
  235. package/dist/src/roles/legacy-role-ids.js +21 -0
  236. package/dist/src/roles/legacy-role-migration.js +62 -0
  237. package/dist/src/roles/loop-ids.js +19 -2
  238. package/dist/src/roles/role-catalog.js +125 -135
  239. package/dist/src/roles/role-guidance.js +64 -31
  240. package/dist/src/roles/role-labels.js +10 -0
  241. package/dist/src/roles/role-render.js +194 -18
  242. package/dist/src/roles/role-view.js +216 -17
  243. package/dist/src/roles/roles.js +18 -15
  244. package/dist/src/scheduling/backoff.js +29 -19
  245. package/dist/src/scheduling/claims.js +25 -9
  246. package/dist/src/scheduling/once-round.js +11 -11
  247. package/dist/src/scheduling/quiet-hours.js +25 -32
  248. package/dist/src/scheduling/scheduling.js +43 -24
  249. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  250. package/dist/src/status/status-budget.js +115 -0
  251. package/dist/src/status/status-data.js +77 -184
  252. package/dist/src/status/status-inbox.js +47 -0
  253. package/dist/src/status/status-install.js +27 -0
  254. package/dist/src/status/status-polls.js +68 -33
  255. package/dist/src/status/status-roles.js +105 -0
  256. package/dist/src/{fleet → team}/error-storm.js +10 -10
  257. package/dist/src/{fleet → team}/failure-spread.js +8 -8
  258. package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
  259. package/dist/src/{fleet → team}/reclaim.js +5 -4
  260. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
  261. package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
  262. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
  263. package/dist/src/text/datetime.js +42 -2
  264. package/dist/src/text/format.js +10 -1
  265. package/dist/src/text/markdown.js +1 -1
  266. package/dist/src/text/phrases.js +35 -34
  267. package/dist/src/text/suggest.js +3 -2
  268. package/dist/src/text/text.js +2 -2
  269. package/dist/src/tick/qa-coverage.js +2 -1
  270. package/dist/src/tick/stage-check.js +147 -26
  271. package/dist/src/tick/telemetry-digest.js +10 -9
  272. package/dist/src/tick/tick-apply.js +101 -72
  273. package/dist/src/tick/tick-detail-data.js +32 -18
  274. package/dist/src/tick/tick-detail.js +62 -21
  275. package/dist/src/tick/tick-finalize.js +29 -22
  276. package/dist/src/tick/tick-outcome.js +2 -1
  277. package/dist/src/tick/tick-prompt.js +94 -26
  278. package/dist/src/tick/tick-resume.js +29 -13
  279. package/dist/src/tick/tick-stage.js +91 -47
  280. package/dist/src/tick/tick-timing.js +60 -59
  281. package/dist/src/tick/tick-usage.js +53 -23
  282. package/dist/src/tick/tick-verdict.js +76 -10
  283. package/dist/src/ui/badges.js +118 -67
  284. package/dist/src/ui/gui/gui-client-boot.js +209 -30
  285. package/dist/src/ui/gui/gui-client-composer.js +102 -23
  286. package/dist/src/ui/gui/gui-client-drawer.js +330 -75
  287. package/dist/src/ui/gui/gui-client-history.js +96 -38
  288. package/dist/src/ui/gui/gui-client-labels.js +24 -0
  289. package/dist/src/ui/gui/gui-client-loops.js +94 -52
  290. package/dist/src/ui/gui/gui-client-markdown.js +1 -0
  291. package/dist/src/ui/gui/gui-client-model.js +63 -43
  292. package/dist/src/ui/gui/gui-client-operator.js +129 -39
  293. package/dist/src/ui/gui/gui-client-pending.js +36 -32
  294. package/dist/src/ui/gui/gui-client-report.js +339 -46
  295. package/dist/src/ui/gui/gui-client-settings.js +148 -14
  296. package/dist/src/ui/gui/gui-client-sound.js +2 -1
  297. package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
  298. package/dist/src/ui/gui/gui-client.js +26 -20
  299. package/dist/src/ui/gui/gui-page.js +19 -19
  300. package/dist/src/ui/gui/gui-styles.js +12 -4
  301. package/dist/src/ui/progress-data.js +5 -3
  302. package/dist/src/ui/status-model.js +184 -122
  303. package/dist/src/ui/status-payload.js +119 -32
  304. package/dist/src/ui/status-render.js +122 -52
  305. package/dist/src/ui/team-alerts.js +341 -0
  306. package/dist/src/ui/tick-progress-model.js +14 -14
  307. package/dist/src/ui/tone.js +11 -8
  308. package/dist/src/ui/transcript-tail.js +1 -0
  309. package/dist/src/ui/transcript.js +23 -16
  310. package/dist/src/ui/tui/tui-app.js +1 -0
  311. package/dist/src/ui/tui/tui-backlog.js +85 -11
  312. package/dist/src/ui/tui/tui-editor-answer.js +97 -0
  313. package/dist/src/ui/tui/tui-editor-attach.js +107 -0
  314. package/dist/src/ui/tui/tui-editor-brief.js +107 -0
  315. package/dist/src/ui/tui/tui-editor-budget.js +155 -0
  316. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  317. package/dist/src/ui/tui/tui-editor-entry.js +230 -0
  318. package/dist/src/ui/tui/tui-editor-interval.js +122 -0
  319. package/dist/src/ui/tui/tui-editor-notes.js +114 -0
  320. package/dist/src/ui/tui/tui-editor-pause.js +266 -0
  321. package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
  322. package/dist/src/ui/tui/tui-editor-queued.js +98 -0
  323. package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
  324. package/dist/src/ui/tui/tui-editor-retire.js +83 -0
  325. package/dist/src/ui/tui/tui-editor-search.js +73 -0
  326. package/dist/src/ui/tui/tui-editor-session.js +97 -0
  327. package/dist/src/ui/tui/tui-editors.js +152 -0
  328. package/dist/src/ui/tui/tui-frame.js +67 -55
  329. package/dist/src/ui/tui/tui-history.js +12 -7
  330. package/dist/src/ui/tui/tui-input.js +49 -44
  331. package/dist/src/ui/tui/tui-keymap.js +1 -0
  332. package/dist/src/ui/tui/tui-keys.js +91 -1704
  333. package/dist/src/ui/tui/tui-pane-activity.js +99 -0
  334. package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
  335. package/dist/src/ui/tui/tui-pane-brief.js +30 -0
  336. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  337. package/dist/src/ui/tui/tui-pane-health.js +124 -0
  338. package/dist/src/ui/tui/tui-pane-history.js +316 -0
  339. package/dist/src/ui/tui/tui-pane-pending.js +74 -0
  340. package/dist/src/ui/tui/tui-pane-queued.js +143 -0
  341. package/dist/src/ui/tui/tui-pane-report.js +122 -0
  342. package/dist/src/ui/tui/tui-pane-session.js +101 -0
  343. package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
  344. package/dist/src/ui/tui/tui-pane.js +66 -28
  345. package/dist/src/ui/tui/tui-panes.js +294 -0
  346. package/dist/src/ui/tui/tui.js +45 -25
  347. package/dist/src/verdict/fix-claim.js +141 -38
  348. package/dist/src/verdict/no-change.js +19 -0
  349. package/dist/src/verdict/refusal.js +92 -8
  350. package/dist/src/verdict/reply-contract.js +17 -5
  351. package/dist/src/version.js +3 -2
  352. package/package.json +4 -2
  353. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  354. package/dist/src/landing/backlog-conflicts.js +0 -116
  355. package/dist/src/landing/landing-drain.js +0 -204
  356. package/dist/src/landing/landing-pipeline.js +0 -96
  357. package/dist/src/landing/landing-queue-render.js +0 -31
  358. package/dist/src/landing/landing-slot.js +0 -246
  359. package/dist/src/landing/landing-vetting.js +0 -196
  360. package/dist/src/pi-extension/role-notes.js +0 -40
  361. package/dist/src/ui/fleet-alerts.js +0 -174
@@ -1,10 +1,12 @@
1
1
  import { loadLoopState, saveLoopState, zeroCounters } from "./loop-state.js";
2
2
  import { DIRECTOR_ROLE } from "../roles/roles.js";
3
3
  import { baseRoleOf } from "../roles/loop-ids.js";
4
- import { warnEventBestEffort } from "../events/events.js";
4
+ import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
5
+ import { promptPreview } from "../inbox/inbox.js";
5
6
  import { assembleTickPrompt } from "../tick/tick-prompt.js";
6
7
  import { LoopPi } from "./loop-pi.js";
7
- import { configForRole, fallbackRoleConfig, seamHoldKey, } from "../config/config-views.js";
8
+ import { configForRole, seamHoldKey, withModelOverrides, } from "../config/config-views.js";
9
+ import { fallbackRoleConfig } from "../config/config-fallbacks.js";
8
10
  import { PendingPrompt } from "../inbox/pending-prompt.js";
9
11
  import { modelFallbackActive } from "./model-fallback.js";
10
12
  import { clearBackoff } from "../scheduling/backoff.js";
@@ -26,20 +28,20 @@ export class LoopRunner {
26
28
  * tick intervals, backoff, and role enablement for subsequent ticks. */
27
29
  config;
28
30
  /** The 429 observation from this loop's usage accounting (TickUsage.lastRateLimit,
29
- * src/tick/tick-usage.ts): the orchestrator's fleet-wide hold wiring reads it through the
30
- * runner (src/fleet/fleet-polls.ts), so the field keeps its place on the runner's surface. */
31
+ * src/tick/tick-usage.ts): the orchestrator's team-wide hold wiring reads it through the
32
+ * runner (src/team/team-polls.ts), so the field keeps its place on the runner's surface. */
31
33
  get lastRateLimit() {
32
34
  return this.usage.lastRateLimit;
33
35
  }
34
36
  /** The backend-failure observation (TickUsage.lastBackendFailure, src/tick/tick-usage.ts): the
35
37
  * non-429 sibling of lastRateLimit above — the connection, timeout, server, and model-load
36
- * kinds the fleet-wide hold groups storms by (src/fleet/fleet-hold.ts). Same runner surface,
38
+ * kinds the team-wide hold groups storms by (src/team/team-hold.ts). Same runner surface,
37
39
  * same consumer. */
38
40
  get lastBackendFailure() {
39
41
  return this.usage.lastBackendFailure;
40
42
  }
41
43
  /** Per-tick and lifetime usage accounting (src/tick/tick-usage.ts): the turns/cost windows the
42
- * commit trailer and tick_end event read, the lifetime totals folded into state, and the
44
+ * commit trailer and run_end event read, the lifetime totals folded into state, and the
43
45
  * observations above. Grown through foldUsage — the once-per-run choke point. */
44
46
  usage = new TickUsage();
45
47
  /** The dequeued user prompt a tick is executing and its requeue policy
@@ -49,7 +51,7 @@ export class LoopRunner {
49
51
  pending;
50
52
  /** Per-tick abort controller, recreated at every tick start: `abortTick()` kills the
51
53
  * in-flight pi run without touching the harness shutdown signal (`this.signal`), which
52
- * would stop the whole fleet. */
54
+ * would stop the whole team. */
53
55
  tickAbort = new AbortController();
54
56
  /** Set by abortTick() when a user-initiated abort lands mid-tick: runTick's two abort
55
57
  * branches (author run, review gate) then diverge from shutdown semantics — worktree reset
@@ -70,7 +72,14 @@ export class LoopRunner {
70
72
  * resolve to, since different roles may run on different pairs.
71
73
  * Transient — never persisted — and captured fresh at every tick start. */
72
74
  tickPair;
73
- /** The landing failure this tick's leftover recovery is retrying, if any (a retriable lander
75
+ /** Whether the budget gate currently has the team on its free fallback pairs (src/gates/
76
+ * budget-gates.ts onFallback, set alongside `config` by the orchestrator's poll): an
77
+ * image reroute may then look only among the free fallback pairs, never a priced primary
78
+ * (plans/image-input.md "Reroute target" "Regime"). Pure scheduler input, never whether
79
+ * the LOOP itself is mid-fallback; defaults false, so standalone-loop tests stay on the
80
+ * primary regime unless they set it. */
81
+ budgetFallbackActive = false;
82
+ /** The merge failure this tick's leftover recovery is retrying, if any (a retriable merger
74
83
  * outcome that kept the pin, which recovery re-queued): set in finishRecoveryTick and
75
84
  * attached to the returned TickOutcome so applyTickOutcome can feed it into the error streak
76
85
  * even though the tick itself ends `queued` (BUGS.md 2026-09-21). Reset at every tick start. */
@@ -102,9 +111,9 @@ export class LoopRunner {
102
111
  signal: this.signal,
103
112
  runSignal: () => this.runSignal(),
104
113
  warn: (message) => this.warn(message),
105
- foldUsage: (run) => this.foldUsage(run),
106
- foldLandingUsage: (run) => this.foldLandingUsage(run),
107
- tickNumber: () => this.state.ticks,
114
+ foldUsage: (run, provider, pair) => this.foldUsage(run, provider, pair),
115
+ foldMergeUsage: (run, provider, pair) => this.foldMergeUsage(run, provider, pair),
116
+ tickNumber: () => this.state.runs,
108
117
  sleep: this.sleep,
109
118
  });
110
119
  // A persisted running flag means the previous process died mid-tick WITHOUT the
@@ -114,6 +123,7 @@ export class LoopRunner {
114
123
  this.state.resumePending = true;
115
124
  this.state.running = false;
116
125
  this.state.parkedSince = undefined;
126
+ this.state.parkedOn = undefined;
117
127
  }
118
128
  save() {
119
129
  saveLoopState(this.root, this.state);
@@ -143,7 +153,7 @@ export class LoopRunner {
143
153
  * when no tick is running — the orchestrator checks `state.running` before calling, and a
144
154
  * direct call must not arm a stale flag. Otherwise set the userAborted flag and abort the
145
155
  * per-tick controller: runPi terminates the in-flight pi child (SIGTERM → SIGKILL
146
- * escalation) exactly like a harness shutdown, but only this loop's run dies — the fleet
156
+ * escalation) exactly like a harness shutdown, but only this loop's run dies — the team
147
157
  * keeps running. */
148
158
  abortTick() {
149
159
  if (!this.state.running)
@@ -173,7 +183,29 @@ export class LoopRunner {
173
183
  * active (a due probe runs the primary), else the role's own resolved config. The hold and
174
184
  * start passes read this so a role mid-episode is keyed by the provider it will actually use.
175
185
  * Pure read of config + state + the caller's clock. */
186
+ /** Which pair set an image reroute may look among (plans/image-input.md "Reroute target"
187
+ * "Regime"): the free fallback pairs while the budget gate is on fallback OR the role is
188
+ * in a model-fallback episode; otherwise the primary tiers. One home for the rule, shared
189
+ * by the target-resolution sites (the verdict's reroute arm, the attachment preflight). */
190
+ rerouteRegime(now) {
191
+ if (this.budgetFallbackActive)
192
+ return "fallback";
193
+ if (modelFallbackActive(this.state.modelFallback, now))
194
+ return "fallback";
195
+ return "primary";
196
+ }
176
197
  runConfig(now) {
198
+ // A pending image-input reroute (plans/image-input.md part 4/5) outranks the role's own
199
+ // tier and its model fallback: the next tick starts on the image-capable target the
200
+ // blind-read tick recorded, rebuilt from the stored seam over the live config.
201
+ if (this.state.imageReroute) {
202
+ const r = this.state.imageReroute;
203
+ return withModelOverrides(this.config, r.tier, {
204
+ provider: r.provider,
205
+ model: r.model,
206
+ thinking: r.thinking,
207
+ });
208
+ }
177
209
  const fallback = fallbackRoleConfig(this.config, this.role);
178
210
  if (fallback !== null && modelFallbackActive(this.state.modelFallback, now))
179
211
  return fallback;
@@ -196,10 +228,13 @@ export class LoopRunner {
196
228
  * runner records it as pending — re-queued if the tick ends without fulfilling it. Null when
197
229
  * the loop has nothing to run (an empty director inbox); a role loop's assembly never
198
230
  * returns null. */
199
- tickPrompt(agent) {
200
- const assembled = assembleTickPrompt({ root: this.root, config: this.config, role: this.role, state: this.state, agent });
231
+ tickPrompt(agent, compact) {
232
+ const assembled = assembleTickPrompt({ root: this.root, config: this.config, role: this.role, state: this.state, agent, compact });
201
233
  if (assembled === null)
202
234
  return null;
235
+ if (assembled.userPrompt !== null) {
236
+ logEventBestEffort(this.root, { source: this.role, type: "prompt_taken", run: this.state.runs, preview: promptPreview(assembled.userPrompt) });
237
+ }
203
238
  this.pending.record(assembled.userPrompt, assembled.userPromptFile);
204
239
  return assembled.prompt;
205
240
  }
@@ -212,45 +247,45 @@ export class LoopRunner {
212
247
  return pinAndResetPhase(this.tickContext(), wt, sha);
213
248
  }
214
249
  /** Thin delegate to finishRecoveryTickPhase (src/loop/loop-recovery.ts). */
215
- async finishRecoveryTick(recovered, userPrompt, wt, priorLandingFailure) {
216
- return finishRecoveryTickPhase(this.tickContext(), recovered, userPrompt, wt, priorLandingFailure);
250
+ async finishRecoveryTick(recovered, userPrompt, wt, priorMergeFailure) {
251
+ return finishRecoveryTickPhase(this.tickContext(), recovered, userPrompt, wt, priorMergeFailure);
217
252
  }
218
253
  /** Fold one pi run's usage into the tick's counters and the lifetime totals — the
219
254
  * accounting itself lives in TickUsage.fold (src/tick/tick-usage.ts); this keeps the once-per-run
220
- * choke point and the foldLandingUsage face on the runner, where LoopPi and the landing
221
- * wiring (src/landing/landing-slot.ts) reach them. */
222
- foldUsage(run) {
223
- this.usage.fold(this.state, run);
255
+ * choke point and the foldMergeUsage face on the runner, where LoopPi and the merge
256
+ * wiring (src/merge/merge-slot.ts) reach them. */
257
+ foldUsage(run, provider, pair) {
258
+ this.usage.fold(this.state, run, true, provider, pair);
224
259
  }
225
- /** Run pi for the orchestrator's landing slot (merge queue 3/5): runRolePi's shared wiring
260
+ /** Run pi for the orchestrator's merge slot (merge queue 3/5): runRolePi's shared wiring
226
261
  * with usage folded into this loop's counters, watching ONLY the harness shutdown signal
227
- * (`this.signal`) — a landing is outside any tick, so `tumwater abort --role` must never
228
- * reach it. The plumbing lives in src/loop/loop-pi.ts; this is the landing slot's public
262
+ * (`this.signal`) — a merge is outside any tick, so `tumwater abort --role` must never
263
+ * reach it. The plumbing lives in src/loop/loop-pi.ts; this is the merge slot's public
229
264
  * face. */
230
- async runLandingPi(wt, prompt, sessionName, config) {
231
- return this.pi.runLandingPi(wt, prompt, sessionName, config);
265
+ async runMergePi(wt, prompt, sessionName, config) {
266
+ return this.pi.runMergePi(wt, prompt, sessionName, config);
232
267
  }
233
- /** Run pi for the landing gate's reviewer (the review run and its verdict follow-up)
268
+ /** Run pi for the merge gate's reviewer (the review run and its verdict follow-up)
234
269
  * through the loop's shared transient-retry wiring — the same retry an authoring run gets,
235
270
  * so a 429 in the gate is waited out and retried once instead of failing the review on
236
271
  * first contact (BUGS.md 2026-10-01). A retried first attempt is folded here at retry time
237
- * (the fleet-wide rate-limit hold's input); the FINAL run's usage is folded by the gate's
272
+ * (the team-wide rate-limit hold's input); the FINAL run's usage is folded by the gate's
238
273
  * caller, exactly as before — the plumbing lives in src/loop/loop-pi.ts; this is the
239
- * landing wiring's public face. */
274
+ * merge wiring's public face. */
240
275
  async runGatePi(opts) {
241
276
  return this.pi.runGatePi(opts);
242
277
  }
243
- /** Public face of foldUsage for the orchestrator's landing wiring: folds a landing pi run
244
- * (reviewer, conflict resolution) into the AUTHORING role's counters, so the fleet's daily
278
+ /** Public face of foldUsage for the orchestrator's merge wiring: folds a merge pi run
279
+ * (reviewer, conflict resolution) into the AUTHORING role's counters, so the team's daily
245
280
  * cost and the per-role usage windows attribute that spend to the role that authored the
246
281
  * work — no new counter semantics, just the same fold loop.ts applies to its tick runs. */
247
- foldLandingUsage(run) {
248
- // authoring=false: a landing/review run's usage counts toward cost and prompt tokens, but
282
+ foldMergeUsage(run, provider, pair) {
283
+ // authoring=false: a merge/review run's usage counts toward cost and prompt tokens, but
249
284
  // never toward the tick's pre-edit counter (src/tick/tick-usage.ts fold).
250
- this.usage.fold(this.state, run, false);
285
+ this.usage.fold(this.state, run, false, provider, pair);
251
286
  }
252
287
  /** Run one full tick of this role loop (the pipeline lives in src/loop/loop-tick.ts):
253
- * `tickPhase` resolves the config and fallback episode, logs tick_start, runs the pipeline,
288
+ * `tickPhase` resolves the config and fallback episode, logs run_start, runs the pipeline,
254
289
  * and finalizes. Never throws: a failed tick is an "error" result, saved and logged. */
255
290
  async tick() {
256
291
  return tickPhase(this.tickContext());
@@ -284,7 +319,8 @@ export class LoopRunner {
284
319
  save: () => this.save(),
285
320
  warn: (message) => this.warn(message),
286
321
  runSignal: () => this.runSignal(),
287
- tickPrompt: (agent) => this.tickPrompt(agent),
322
+ tickPrompt: (agent, compact) => this.tickPrompt(agent, compact),
323
+ rerouteRegime: (now) => this.rerouteRegime(now),
288
324
  get recoveryFailure() {
289
325
  return self.recoveryFailure;
290
326
  },
@@ -305,7 +341,7 @@ export class LoopRunner {
305
341
  finishAbortedTick: (userPrompt, wt) => this.finishAbortedTick(userPrompt, wt),
306
342
  pinAndReset: (wt, sha) => this.pinAndReset(wt, sha),
307
343
  foldModelFallback: (fallbackCtx, pi) => this.foldModelFallback(fallbackCtx, pi),
308
- finishRecoveryTick: (recovered, userPrompt, wt, priorLandingFailure) => this.finishRecoveryTick(recovered, userPrompt, wt, priorLandingFailure),
344
+ finishRecoveryTick: (recovered, userPrompt, wt, priorMergeFailure) => this.finishRecoveryTick(recovered, userPrompt, wt, priorMergeFailure),
309
345
  };
310
346
  }
311
347
  }
@@ -15,11 +15,12 @@ export function modelFallbackProbe(state, now) {
15
15
  return state !== undefined && state.since > 0 && now >= state.probeAt;
16
16
  }
17
17
  /** Classify one authoring run's verdict about the primary (src/tick/tick-usage.ts records the
18
- * same flags for the fleet-wide hold). A run that was aborted, quiet-killed, or timed out is
18
+ * same flags for the team-wide hold). A run that was aborted, quiet-killed, or timed out — or
19
+ * that read an image its model cannot see (the runner killed it, plans/image-input.md) — is
19
20
  * inconclusive even when it also reports provider evidence: the harness killed it, so it is
20
21
  * no verdict about the primary's health. */
21
22
  export function modelFallbackVerdict(pi) {
22
- if (pi.aborted || pi.quietKilled || pi.timedOut)
23
+ if (pi.aborted || pi.quietKilled || pi.timedOut || pi.imageBlind)
23
24
  return "inconclusive";
24
25
  if (!pi.ok && (pi.transientRateLimit || pi.transientBackend))
25
26
  return "provider-failure";
@@ -1,15 +1,24 @@
1
1
  import { gitTry } from "../git/git-run.js";
2
2
  import { resetWorktreeToMain } from "../git/worktree.js";
3
- import { conflictedFiles } from "../landing/landing-git.js";
4
- /** The most revision rounds one rejected change gets before its rejection is final
5
- * (plans/revise-rejected.md): the gate rejected the change, the author re-applies the rejected
6
- * diff onto current main, fixes the named objections and re-submits. Past this the change is
7
- * dropped and a plain rejection stands, so a non-converging author cannot revise forever. */
8
- export const REVISION_LIMIT = 2;
3
+ import { conflictedFiles } from "../merge/merge-git.js";
4
+ /** The persistence count at which one repeated objection makes a rejection final
5
+ * (plans/revise-rejected.md; the repeat-finality policy in BUGS.md 2026-10-14): each numbered
6
+ * reason records how many consecutive revisions it has survived — a new (untagged) objection is
7
+ * 0, a `(prior N)` restatement is 1 plus the prior round's count for that objection — and when
8
+ * any reason reaches this count it was raised and then survived two revisions, so the change has
9
+ * stopped converging and the rejection is final. A converging chain keeps revising while each
10
+ * round's objections differ. */
11
+ export const REVISION_FINAL_ON_REPEAT = 2;
12
+ /** The hard backstop on one change's revision chain (plans/revise-rejected.md; BUGS.md
13
+ * 2026-10-14): at this many total rejections the rejection is final even when the objections
14
+ * never repeat, so a non-converging author cannot revise forever. Replaces the old fixed
15
+ * `REVISION_LIMIT = 2`, which ended a converging chain at its third rejection whenever each
16
+ * round raised a new objection. */
17
+ export const REVISION_BACKSTOP = 6;
9
18
  /** Re-apply a rejected change `sha` onto current main as uncommitted edits in `wt`
10
19
  * (plans/revise-rejected.md): the author's next tick starts from the rejected diff instead of
11
20
  * re-deriving it. `git cherry-pick --no-commit <merge-base>..<sha>` leaves the edits in the
12
- * worktree without committing. On any git failure — most often a conflict with what main landed
21
+ * worktree without committing. On any git failure — most often a conflict with what main merged
13
22
  * since the rejection, but also a missing object — the cherry-pick is aborted and the worktree
14
23
  * reset to main, and false is returned so the caller falls back to a plain rejection note. */
15
24
  export async function applyRevision(wt, mainBranch, sha) {
@@ -0,0 +1,155 @@
1
+ /** Deterministic first pass over a conflicted rebase for the backlog markdown files
2
+ * (plans/parallel-work-instances.md, "Insert-only backlog conflicts", part 3/7).
3
+ *
4
+ * Every finished entry lands as the first one under `## Done` or `## Fixed` — at the top of
5
+ * PLANS-DONE.md or BUGS-FIXED.md once tumwater archives it (plans/backlog-archive.md) — so two
6
+ * merges off the same base insert different entries at the same location. Git's diff3 conflict
7
+ * for that shape has an empty base section and both sides non-empty; keeping both inserted sides
8
+ * adds no authored bytes, so no model run is needed.
9
+ *
10
+ * One more shape settles the same way: inside a live `## Done`/`## Fixed` section, the change
11
+ * added whole `### ` entries right above lines that main has since removed or rewritten — a
12
+ * change committed before its repository's history moved to the archive, whose entries sat
13
+ * right below the insertion point. The change's side is then its new entries followed by
14
+ * exactly the base lines, and keeping those entries on top of main's side again adds no
15
+ * authored bytes. Anything else — the same entry edited on both sides, a code file, a deleted
16
+ * side — goes to the pi resolver untouched. */
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import { BACKLOG_FILES, FINISHED_ARCHIVES } from "../backlog/backlog-md.js";
20
+ import { writeTextAtomic } from "../files/files.js";
21
+ import { git } from "../git/git-run.js";
22
+ /** The lines the change inserted in `hunk`, when it is one of the two shapes this pass settles,
23
+ * or null. Insert-only: an empty base section and both sides carrying lines — all of the
24
+ * change's side. Entries above a rewritten history (see the module comment): the hunk sits in a
25
+ * finished section, and the change's side is whole `### ` entries followed by exactly the base
26
+ * lines — the part before them. */
27
+ function insertedLines(hunk, lines) {
28
+ if (hunk.base.length === 0)
29
+ return hunk.ours.length > 0 && hunk.theirs.length > 0 ? hunk.theirs : null;
30
+ if (!inFinishedSection(lines, hunk.start) || hunk.theirs.length <= hunk.base.length)
31
+ return null;
32
+ const cut = hunk.theirs.length - hunk.base.length;
33
+ if (hunk.theirs.slice(cut).some((line, i) => line !== hunk.base[i]))
34
+ return null;
35
+ const inserted = hunk.theirs.slice(0, cut);
36
+ const first = inserted.find((line) => line.trim() !== "");
37
+ return first?.startsWith("### ") ? inserted : null;
38
+ }
39
+ /** True when the nearest `## ` heading above line `at` is a finished section (`## Done` or
40
+ * `## Fixed`), with no conflict marker in between — an earlier hunk's sides leave the section
41
+ * unknown, so the answer is then no. */
42
+ function inFinishedSection(lines, at) {
43
+ for (let i = at - 1; i >= 0; i--) {
44
+ const line = lines[i] ?? "";
45
+ if (/^(<{7}|\|{7}|={7}|>{7})/.test(line))
46
+ return false;
47
+ if (line.startsWith("## "))
48
+ return FINISHED_SECTIONS.has(line.slice(3).trim());
49
+ }
50
+ return false;
51
+ }
52
+ /** The finished sections' titles, from FINISHED_ARCHIVES. */
53
+ const FINISHED_SECTIONS = new Set(FINISHED_ARCHIVES.map((a) => a.section));
54
+ /** Rewrite every settleable conflict hunk in `text` and return the result, or null when any
55
+ * hunk is neither shape insertedLines accepts (or the markers are malformed) — the caller then
56
+ * leaves the file for the model resolver. Each hunk is replaced by the lines the change (theirs)
57
+ * inserted, a blank separator when neither side already supplies one, then main's (ours)
58
+ * lines, so the later merge stays first. */
59
+ export function resolveInsertOnlyText(text) {
60
+ const lines = text.split("\n");
61
+ const hunks = parseHunks(lines);
62
+ if (hunks === null)
63
+ return null;
64
+ if (hunks.length === 0)
65
+ return null;
66
+ const inserted = hunks.map((hunk) => insertedLines(hunk, lines));
67
+ if (inserted.some((ins) => ins === null))
68
+ return null;
69
+ // Replace from the end so earlier indices stay valid.
70
+ for (let h = hunks.length - 1; h >= 0; h--) {
71
+ const hunk = hunks[h];
72
+ const theirs = inserted[h];
73
+ const separated = hunk.ours.length === 0 || theirs[theirs.length - 1] === "" || hunk.ours[0] === "";
74
+ const replacement = separated ? [...theirs, ...hunk.ours] : [...theirs, "", ...hunk.ours];
75
+ lines.splice(hunk.start, hunk.end - hunk.start + 1, ...replacement);
76
+ }
77
+ return lines.join("\n");
78
+ }
79
+ /** Split `lines` into conflict hunks. Returns null when a marker sequence is malformed (an
80
+ * unterminated hunk, or a missing `|||||||`/`=======`), so the caller can bail out. */
81
+ function parseHunks(lines) {
82
+ const hunks = [];
83
+ let i = 0;
84
+ while (i < lines.length) {
85
+ if (!lines[i].startsWith("<<<<<<<")) {
86
+ i++;
87
+ continue;
88
+ }
89
+ const start = i;
90
+ const ours = [];
91
+ const base = [];
92
+ const theirs = [];
93
+ // Consume each side up to its marker (the `<<<<<<<` line itself is skipped by starting
94
+ // at i + 1); a missing marker means the diff3 hunk is malformed and the parse aborts.
95
+ const afterOurs = collectUntilMarker(lines, i + 1, "|||||||", ours);
96
+ if (afterOurs === null)
97
+ return null; // no base marker
98
+ const afterBase = collectUntilMarker(lines, afterOurs, "=======", base);
99
+ if (afterBase === null)
100
+ return null; // no separator
101
+ const afterTheirs = collectUntilMarker(lines, afterBase, ">>>>>>>", theirs);
102
+ if (afterTheirs === null)
103
+ return null; // no end marker
104
+ hunks.push({ start, end: afterTheirs - 1, ours, base, theirs });
105
+ i = afterTheirs;
106
+ }
107
+ return hunks;
108
+ }
109
+ /** Append lines from `start` into `into` until one starts with `marker` (the marker itself is
110
+ * consumed, not kept), and return the index just past it. Returns null when no line from
111
+ * `start` on carries the marker — the malformed-hunk signal parseHunks turns into a bail-out.
112
+ * The one home of the drain-until-marker step shared by parseHunks's three sides, so the
113
+ * marker-consumption rule cannot drift between `|||||||`, `=======`, and `>>>>>>>`. */
114
+ function collectUntilMarker(lines, start, marker, into) {
115
+ let i = start;
116
+ while (i < lines.length && !lines[i].startsWith(marker))
117
+ into.push(lines[i++]);
118
+ return i >= lines.length ? null : i + 1;
119
+ }
120
+ /** Settle the insert-only conflicts among `files` in `wt` (plans/parallel-work-instances.md,
121
+ * part 3/7): every conflicted repo-root backlog file (BACKLOG_FILES: PLANS.md, BUGS.md,
122
+ * QUESTIONS.md and the two archive files) whose hunks all settle is rewritten without markers and
123
+ * staged. Returns the files still conflicted —
124
+ * every file that was not fully settled, including non-backlog files and files with any other
125
+ * hunk — so the caller runs the model resolver only over those. */
126
+ export async function resolveBacklogInsertConflicts(wt, files) {
127
+ const remaining = [];
128
+ for (const file of files) {
129
+ if (path.dirname(file) !== "." || !BACKLOG_FILES.has(file)) {
130
+ remaining.push(file);
131
+ continue;
132
+ }
133
+ const p = path.join(wt, file);
134
+ let text;
135
+ try {
136
+ text = fs.readFileSync(p, "utf8");
137
+ }
138
+ catch {
139
+ remaining.push(file); // deleted side or unreadable path — leave it to the resolver
140
+ continue;
141
+ }
142
+ const resolved = resolveInsertOnlyText(text);
143
+ if (resolved === null) {
144
+ remaining.push(file);
145
+ continue;
146
+ }
147
+ // Atomic (tmp + rename): a merge process killed mid-write (SIGKILL, the timeout
148
+ // group-kill) must leave either the conflicted file or the fully resolved one, never a
149
+ // truncated backlog — the CLI's backlog writers (backlog-write.ts, question-answer.ts)
150
+ // already write these unbounded-history files through this helper.
151
+ writeTextAtomic(p, resolved);
152
+ await git(wt, "add", "--", file);
153
+ }
154
+ return remaining;
155
+ }