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,7 +1,8 @@
1
- /** The landing gate's deterministic checks, run BEFORE a changed tick commits.
1
+ // vocabulary: plain
2
+ /** The merge gate's deterministic checks, run BEFORE a changed tick commits.
2
3
  *
3
- * The gate runs `backlogStructureReason` and `falseFixReason` at landing time, by which point
4
- * the author's session is over: a finding costs a queue slot, a vet, and a rejection that
4
+ * The gate runs `backlogStructureReason` and `falseFixReason` at merge time, by which point
5
+ * the author's session is over: a finding costs a queue slot, a verification, and a rejection that
5
6
  * discards the work (13 phantom-fix rejections between 2026-09-23 and 2026-10-02). Both checks
6
7
  * read the worktree and the change's merge-base, so the same questions can be asked at staging
7
8
  * time, while the authoring session can still be continued for one fix-up turn. This module
@@ -17,15 +18,32 @@
17
18
  * files whose final newline the change removed. These stage the change (`git add -A`) to read
18
19
  * the index with git only, then restore the index to HEAD before returning: staging is a read
19
20
  * aid for the checks, not a change to the tick's state. A git hiccup yields no finding rather
20
- * than failing the tick. */
21
+ * than failing the tick. Unlike the backlog checks above, the gate does not run these: they are
22
+ * heuristics for what a reviewer tends to reject, so neither path check looks inside record
23
+ * prose (isRecordProse), where an old or not-yet-created path is correct as written.
24
+ *
25
+ * `planDependencyFinding` is staging-only: it is not part of the review gate or the in-lock
26
+ * merge re-check, because `tumwater doctor`'s plan-dependency warning is the backstop for
27
+ * anything that gets past the fix-up turn. `waitingNoteFinding` is staging-only and bugfix-only
28
+ * for the same reason: it nudges a bugfix run that left a Progress note without the hold. */
29
+ import path from "node:path";
21
30
  import { changedFiles } from "../git/git-diff.js";
22
- import { conflictMarkerReason } from "../landing/landing-git.js";
31
+ import { conflictMarkerReason } from "../merge/merge-git.js";
23
32
  import { isExemptDiff } from "../review/exemptions.js";
24
33
  import { backlogStructureReason } from "../backlog/backlog-structure.js";
34
+ import { protectedEditReason } from "../review/protected-edits.js";
35
+ import { secretLeakReason } from "../review/secret-leaks.js";
25
36
  import { falseFixReason } from "../verdict/fix-claim.js";
26
- import { changeBaseRev } from "../git/git.js";
37
+ import { changeBaseRev, fileContentAt } from "../git/git.js";
38
+ import { readTextOrNull } from "../files/files.js";
39
+ import { BACKLOG_FILES, finishedArchiveOf, parseEntryDetails } from "../backlog/backlog-md.js";
40
+ import { entryKey, planDependencyProblems, refusalNote, waitingNote } from "../backlog/backlog-eligibility.js";
41
+ import { baseRoleOf } from "../roles/loop-ids.js";
42
+ import { WAITING_FOR_FAILURE_NOTE } from "../roles/role-guidance.js";
27
43
  import { gitTry } from "../git/git-run.js";
28
44
  import { moreSuffix } from "../text/phrases.js";
45
+ /** Where finished plans are archived (plans/backlog-archive.md). */
46
+ const PLANS_ARCHIVE = finishedArchiveOf("PLANS.md").archive;
29
47
  /** How many `file:line` hits a single stale-reference finding lists before it stops. */
30
48
  const MAX_STALE_HITS = 10;
31
49
  /** How many renamed/deleted paths each stale-reference finding reports before stopping — the
@@ -41,6 +59,17 @@ const MAX_NEWLINE_FILES = 10;
41
59
  function diffPlusPath(line) {
42
60
  return line.slice(4).replace(/^b\//, "");
43
61
  }
62
+ /** True for record prose: a backlog file (BACKLOG_FILES: PLANS.md, BUGS.md, QUESTIONS.md and the
63
+ * finished-entry archives) or a markdown design doc under `plans/`. They record history and
64
+ * describe planned work, so a path there is correct as written even when it is absent from the
65
+ * tree: a finished entry names a file as it was, and a plan names the file it will create or
66
+ * both sides of the move it describes. Asking a rename to sweep them rewrote "old → new" into
67
+ * "new → new" and history into the present; that failure took five rejections on
68
+ * one rename on 2026-10-18 and -19. */
69
+ function isRecordProse(file) {
70
+ const base = file.slice(file.lastIndexOf("/") + 1);
71
+ return BACKLOG_FILES.has(base) || (file.startsWith("plans/") && file.endsWith(".md"));
72
+ }
44
73
  /** The capped list a finding line appends: at most `max` entries joined by ", ", then
45
74
  * `moreSuffix` for the rest. Shared by the missing-path and lost-final-newline findings so both
46
75
  * cap and join identically. */
@@ -48,13 +77,78 @@ function cappedList(items, max) {
48
77
  const shown = items.slice(0, max);
49
78
  return `${shown.join(", ")}${moreSuffix(items.length - shown.length)}`;
50
79
  }
51
- /** The deterministic findings the landing gate would reject this uncommitted change for, or an
52
- * empty list when it is clean. Checks the worktree at `wt` against `mainBranch`'s merge-base;
53
- * `exemptPaths` are the config's review-exemption globs that scope the false-fix check.
80
+ /** The finding for the heading `requires` clause problems that `head` (the edited PLANS.md)
81
+ * adds over `base` (its text at the merge-base), or undefined when it adds none; `archivedTitles`
82
+ * (PLANS-DONE.md's headings) count as existing headings on both sides. Only new
83
+ * problems count: one already on main is not this run's fault and stays doctor's to report, so
84
+ * the author is never asked to fix another run's heading. The most serious new problem is named
85
+ * — cycle, then dangling, unparsed, then misworded — with a count of the rest. */
86
+ export function planDependencyFinding(head, base, archivedTitles = []) {
87
+ const problems = (text) => planDependencyProblems(parseEntryDetails(text, "Planned"), [...parseEntryDetails(text, "Done").map((e) => e.title), ...archivedTitles]);
88
+ const was = problems(base);
89
+ const now = problems(head);
90
+ const newCycle = now.cycle !== null && was.cycle === null ? now.cycle : null;
91
+ const newDangling = now.dangling.filter((d) => !was.dangling.some((b) => b.title === d.title && b.ref === d.ref));
92
+ const newUnparsed = now.unparsed.filter((t) => !was.unparsed.includes(t));
93
+ const newMisworded = now.misworded.filter((m) => !was.misworded.some((b) => b.title === m.title && b.ref === m.ref));
94
+ const total = (newCycle !== null ? 1 : 0) + newDangling.length + newUnparsed.length + newMisworded.length;
95
+ if (total === 0)
96
+ return undefined;
97
+ let first;
98
+ if (newCycle !== null) {
99
+ first = `PLANS.md's requires clauses now form a cycle: ${newCycle.join(" → ")} — every part on it stays blocked forever; ` + // vocabulary-ok: PLANS.md heading syntax
100
+ "edit one heading's `requires … landed` clause to break it"; // vocabulary-ok: PLANS.md heading syntax
101
+ }
102
+ else if (newDangling.length > 0) {
103
+ const d = newDangling[0];
104
+ first = `PLANS.md plan "${d.title}" requires ${d.ref}, which no PLANS.md heading carries — the claim allocator ` +
105
+ "ignores the clause and hands the plan out now; fix the series name or part number to match the prerequisite's heading";
106
+ }
107
+ else if (newUnparsed.length > 0) {
108
+ first = `PLANS.md plan "${newUnparsed[0]}" has a \`requires … landed\` clause that names no part the claim allocator can read — ` + // vocabulary-ok: PLANS.md heading syntax
109
+ "it holds nothing; write each ref as `<Series> i/n` or `part i/n` after a `<Series>, part i/n:` heading prefix";
110
+ }
111
+ else {
112
+ const w = newMisworded[0];
113
+ first = `PLANS.md plan "${w.title}" names ${w.ref} as a prerequisite without a \`requires … landed\` clause — ` + // vocabulary-ok: PLANS.md heading syntax
114
+ "the claim allocator ignores that wording and hands the part out now; write it as `requires part i/n landed` — fix it before this change commits"; // vocabulary-ok: PLANS.md heading syntax
115
+ }
116
+ return `${first}${moreSuffix(total - 1)}`;
117
+ }
118
+ /** The number of `- **Progress …` lines in an entry body. */
119
+ function progressCount(body) {
120
+ return body.split("\n").filter((l) => /^-\s+\*\*Progress\b/.test(l.trim())).length;
121
+ }
122
+ /** The finding for a BUGS.md bug that `head` (the edited file) shows with more Progress notes
123
+ * than `base` (its text at the merge-base) while it stays under `## Open` with neither a Waiting
124
+ * note nor a Refused note — bugfix's step 2 asks a run that cannot reproduce a bug to write the
125
+ * Waiting note, and runs that skip it leave the bug open to re-investigation. A newly filed bug
126
+ * is not a re-investigation. Names the first such bug in file order, with a count of the rest. */
127
+ export function waitingNoteFinding(head, base) {
128
+ const was = new Map(parseEntryDetails(base, "Open").map((e) => [entryKey(e.title), e.body]));
129
+ const counting = parseEntryDetails(head, "Open").filter((e) => {
130
+ const before = was.get(entryKey(e.title));
131
+ return (before !== undefined &&
132
+ progressCount(e.body) > progressCount(before) &&
133
+ waitingNote(e.body) === null &&
134
+ refusalNote(e.body) === null);
135
+ });
136
+ if (counting.length === 0)
137
+ return undefined;
138
+ return (`BUGS.md bug "${counting[0].title}" gained a Progress note but stays open with no Waiting for the next ` +
139
+ `failure note — if this run could not reproduce it, add ${WAITING_FOR_FAILURE_NOTE} as its own line under ` +
140
+ `the heading so later runs wait for new evidence (bugfix's step 2); if it did reproduce, leave the entry ` +
141
+ `as it is${moreSuffix(counting.length - 1)}`);
142
+ }
143
+ /** The findings likely to get this uncommitted change rejected at merge, or an empty list when
144
+ * it is clean: the merge gate's own deterministic checks plus the staging-only heuristics (plan
145
+ * dependencies, the bugfix Waiting note, path drift). Checks the worktree at `wt` against
146
+ * `mainBranch`'s merge-base; `exemptPaths` are the config's review-exemption globs that scope
147
+ * the false-fix check.
54
148
  *
55
149
  * Never throws: a check that fails yields no finding, so a git hiccup at staging time defers to
56
150
  * the gate instead of failing the tick. */
57
- export async function stageCheckFindings(wt, mainBranch, exemptPaths) {
151
+ export async function stageCheckFindings(wt, mainBranch, exemptPaths, role) {
58
152
  const files = await changedFiles(wt).catch(() => null);
59
153
  if (files === null || files.length === 0)
60
154
  return [];
@@ -62,6 +156,29 @@ export async function stageCheckFindings(wt, mainBranch, exemptPaths) {
62
156
  const structure = await backlogStructureReason(wt, mainBranch, files).catch(() => undefined);
63
157
  if (structure)
64
158
  findings.push(structure);
159
+ if (files.includes("PLANS.md")) {
160
+ const head = readTextOrNull(path.join(wt, "PLANS.md")) ?? "";
161
+ const base = await fileContentAt(wt, await changeBaseRev(wt, mainBranch), "PLANS.md").catch(() => undefined);
162
+ // Finished plans are archived in PLANS-DONE.md (plans/backlog-archive.md): a requires clause
163
+ // naming a merged part resolves there, on both sides of the comparison alike.
164
+ const archived = parseEntryDetails(readTextOrNull(path.join(wt, PLANS_ARCHIVE)) ?? "", "Done").map((e) => e.title);
165
+ const dependency = base === undefined ? undefined : planDependencyFinding(head, base, archived);
166
+ if (dependency !== undefined)
167
+ findings.push(dependency);
168
+ }
169
+ if (baseRoleOf(role) === "bugfix" && files.includes("BUGS.md")) {
170
+ const head = readTextOrNull(path.join(wt, "BUGS.md")) ?? "";
171
+ const base = await fileContentAt(wt, await changeBaseRev(wt, mainBranch), "BUGS.md").catch(() => undefined);
172
+ const waiting = base === undefined ? undefined : waitingNoteFinding(head, base);
173
+ if (waiting !== undefined)
174
+ findings.push(waiting);
175
+ }
176
+ const protectedReason = await protectedEditReason(wt, mainBranch, files, role).catch(() => undefined);
177
+ if (protectedReason)
178
+ findings.push(protectedReason);
179
+ const secretReason = await secretLeakReason(wt, mainBranch, files).catch(() => undefined);
180
+ if (secretReason)
181
+ findings.push(secretReason);
65
182
  if (isExemptDiff(files, exemptPaths)) {
66
183
  const falseFix = await falseFixReason(wt, mainBranch, files).catch(() => undefined);
67
184
  if (falseFix)
@@ -77,11 +194,11 @@ export async function stageCheckFindings(wt, mainBranch, exemptPaths) {
77
194
  * (plans/parallel-work-instances.md "Claims", part 4/7): the assigned entry is the instance's
78
195
  * one task, so moving another is the same class of mistake `backlogStructureReason` catches
79
196
  * and costs one fix-up turn before the change commits. undefined when nothing moved or the
80
- * claimed entry itself moved — a landed entry is the expected way to release a claim. */
197
+ * claimed entry itself moved — a merged entry is the expected way to release a claim. */
81
198
  export function assignedMovedFinding(claimKey, moved) {
82
199
  if (moved.length === 0 || moved.some((m) => m.key === claimKey))
83
200
  return undefined;
84
- return `this tick is assigned one backlog entry but moved a different one: "${moved[0].title}"`;
201
+ return `this run is assigned one backlog entry but moved a different one: "${moved[0].title}"`;
85
202
  }
86
203
  /** The staging finding for an unassigned tick that moved a backlog entry a sibling instance
87
204
  * holds — one the poll listed in the tick's `<held-entries>` note (LoopState.heldElsewhere).
@@ -89,13 +206,13 @@ export function assignedMovedFinding(claimKey, moved) {
89
206
  * point as assignedMovedFinding (BUGS.md 2026-10-12: 24 of 29 staged claims duplicated a
90
207
  * sibling's held entry, and each duplicate cost a gate check, a review, and a conflict
91
208
  * resolver run holding the merge slot). The fix-up turn is asked to drop the duplicate work.
92
- * A change that held nothing else then ends with no change to land. undefined when no moved
209
+ * A change that held nothing else then ends with no change to merge. undefined when no moved
93
210
  * entry is held. */
94
211
  export function heldMovedFinding(heldKeys, moved) {
95
212
  const dup = moved.find((m) => heldKeys.has(m.key));
96
213
  if (dup === undefined)
97
214
  return undefined;
98
- return (`this tick moved a backlog entry a sibling instance holds: "${dup.title}" — it is that ` +
215
+ return (`this run moved a backlog entry another instance of this role is working on: "${dup.title}" — it is that ` +
99
216
  `instance's task (see <held-entries>), so revert every edit made for it, including the ` +
100
217
  `entry's move, and leave nothing behind if that was the whole change`);
101
218
  }
@@ -138,7 +255,7 @@ async function gitLevelFindings(wt, mainBranch) {
138
255
  }
139
256
  finally {
140
257
  // Restore the index to HEAD. Without this, the tick's change detection (changedFiles and
141
- // stageTickLanding's "nothing left to land" guard) reads the stale index and reports a
258
+ // stageTickMerge's "nothing left to merge" guard) reads the stale index and reports a
142
259
  // fix-up that reverted the whole change as still changed, so an empty tree would reach
143
260
  // commitAll instead of the tick ending no_change.
144
261
  await gitTry(wt, "reset");
@@ -146,7 +263,8 @@ async function gitLevelFindings(wt, mainBranch) {
146
263
  }
147
264
  /** (a) References left behind by a rename or delete: for each `D` path and each `R` origin,
148
265
  * `git grep --cached` the old path across the staged tree and report up to MAX_STALE_HITS
149
- * `file:line` hits. Renames report the path they moved to; deletes report the deletion. */
266
+ * `file:line` hits outside record prose (isRecordProse). Renames report the path they moved to;
267
+ * deletes report the deletion. */
150
268
  async function staleReferenceFindings(wt, entries) {
151
269
  const findings = [];
152
270
  let reported = 0;
@@ -164,7 +282,7 @@ async function staleReferenceFindings(wt, entries) {
164
282
  const hits = [];
165
283
  for (const line of out.split("\n")) {
166
284
  const m = /^([^:]+):(\d+):(.*)$/.exec(line);
167
- if (!m || !hasStandalonePathOccurrence(m[3] ?? "", old))
285
+ if (!m || isRecordProse(m[1]) || !hasStandalonePathOccurrence(m[3] ?? "", old))
168
286
  continue;
169
287
  hits.push(`${m[1]}:${m[2]}`);
170
288
  if (hits.length >= MAX_STALE_HITS)
@@ -198,6 +316,13 @@ function hasStandalonePathOccurrence(line, needle) {
198
316
  }
199
317
  return false;
200
318
  }
319
+ /** Each distinct path-shaped token in `text`, in first-appearance order: at least one slash and
320
+ * a final `.<ext>` segment. Scanned inside the text (not whitespace-split) so surrounding
321
+ * backticks, brackets, and sentence punctuation fall away with the match. */
322
+ export function pathTokens(text) {
323
+ const tokenRe = /[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+\.[A-Za-z0-9]+/g;
324
+ return [...new Set([...text.matchAll(tokenRe)].map((m) => m[0]))];
325
+ }
201
326
  /** (b) Nonexistent paths: every path-shaped token on an added diff line whose first segment is
202
327
  * a tracked top-level directory but which is absent from the staged tree. Rename origins and
203
328
  * deleted paths are excluded — "moved from X" prose names them legitimately. */
@@ -222,10 +347,6 @@ async function missingPathFindings(wt, diff, entries) {
222
347
  }
223
348
  const seen = new Set();
224
349
  const missing = [];
225
- // A path-shaped token: at least one slash and a final `.<ext>` segment. Scanned inside the
226
- // line (not whitespace-split) so surrounding backticks, brackets, and sentence punctuation
227
- // fall away with the match.
228
- const tokenRe = /[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+\.[A-Za-z0-9]+/g;
229
350
  let file = null;
230
351
  for (const line of diff.split("\n")) {
231
352
  if (line.startsWith("+++ ")) {
@@ -235,12 +356,12 @@ async function missingPathFindings(wt, diff, entries) {
235
356
  if (!line.startsWith("+") || line.startsWith("+++"))
236
357
  continue;
237
358
  // A test file's fixtures routinely name paths from the software under test, which need
238
- // not exist here; the added-nonexistent-path heuristic targets real references, so it
239
- // spares added lines in test/ (the rename/delete scan above still covers the file).
240
- if (file?.startsWith("test/"))
359
+ // not exist here, and record prose names the paths a plan proposes to create; the
360
+ // added-nonexistent-path heuristic targets real references, so it spares added lines in
361
+ // test/ and in record prose (isRecordProse).
362
+ if (file !== null && (file.startsWith("test/") || isRecordProse(file)))
241
363
  continue;
242
- for (const m of line.slice(1).matchAll(tokenRe)) {
243
- const p = m[0];
364
+ for (const p of pathTokens(line.slice(1))) {
244
365
  const top = p.slice(0, p.indexOf("/"));
245
366
  if (!topDirs.has(top) || oldPaths.has(p) || staged.has(p) || seen.has(p))
246
367
  continue;
@@ -1,22 +1,23 @@
1
1
  /** The `telemetry` role's tick-time evidence: the failure digest rendered over the role's own
2
- * one-day window. Split out of src/failure/failure-render.ts — which stays the pure
2
+ * trailing 24 h window. Split out of src/failure/failure-render.ts — which stays the pure
3
3
  * renderer — so every observer role's evidence builder has its own module the way
4
4
  * qa-coverage.ts and backlog-structure.ts do, and the tick lifecycle (tick-prompt.ts) injects
5
5
  * it from there instead of reaching into a report module. */
6
- import { collectFailureReport } from "../failure/failure-data.js";
6
+ import { collectFailureReportSince } from "../failure/failure-data.js";
7
7
  import { renderFailureMarkdown } from "../failure/failure-render.js";
8
- /** The `telemetry` role's own digest window, in local calendar days (plans/telemetry-role.md).
9
- * The CLI keeps the usage report's 14-day default; the role reads one day so a cluster
10
- * re-surfaces only while it is live. The 2×-window read below still spans two days, which is
11
- * what makes the delta line meaningful. */
12
- export const TELEMETRY_DIGEST_DAYS = 1;
8
+ /** The `telemetry` role's own digest window: the trailing 24 h ending now, not the local
9
+ * calendar day. A calendar day starts at midnight, so a run at 06:32 would never see the
10
+ * previous evening. The CLI keeps the usage report's 14-day default; the role reads 24 h so a
11
+ * cluster re-surfaces only while it is live. The collector's equal-length prior window is what
12
+ * makes the delta line meaningful. */
13
+ export const TELEMETRY_DIGEST_MS = 24 * 60 * 60 * 1000;
13
14
  /** The `telemetry` role's tick-time evidence: the failure digest rendered over its own
14
- * one-day window. A missing or corrupt log omits the block (undefined) and never fails the
15
+ * trailing 24 h window. A missing or corrupt log omits the block (undefined) and never fails the
15
16
  * tick — an observer must not break on bookkeeping (plans/telemetry-role.md). The window and
16
17
  * the swallow-errors policy live with the digest, not in the tick lifecycle that injects it. */
17
18
  export function telemetryDigest(root) {
18
19
  try {
19
- return renderFailureMarkdown(collectFailureReport(root, TELEMETRY_DIGEST_DAYS));
20
+ return renderFailureMarkdown(collectFailureReportSince(root, TELEMETRY_DIGEST_MS));
20
21
  }
21
22
  catch {
22
23
  return undefined;