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,35 +1,34 @@
1
- // vocabulary: plain
2
1
  /** The role registries and classification helpers over the built-in role catalog. The catalog
3
- * itself — each loop's identity and its "find something to do" prompt text — lives in
2
+ * itself — each role's identity and its "find something to do" prompt text — lives in
4
3
  * role-catalog.ts (split out 2026-10-01, organize); this module re-exports ROLES and Role so
5
4
  * every importer keeps the single roles.js import path, and derives from them the scheduling
6
5
  * registries (work/maintenance/observer tiers, deferrable and baseline-blocked sets) and the
7
- * lookup helpers the CLI, GUI, and tick prompt share. */
6
+ * lookup helpers the CLI, GUI, and run prompt share. */
8
7
  import { ROLES } from "./role-catalog.js";
9
- import { baseRoleOf } from "./loop-ids.js";
8
+ import { baseRoleOf } from "./runner-ids.js";
10
9
  import { typoSuffix } from "../text/suggest.js";
11
10
  export { ROLES };
12
- /** The director loop's id — a target that is not in ROLES (allRoleIds appends it) and is
11
+ /** The director role's id — a target that is not in ROLES (allRoleIds appends it) and is
13
12
  * driven by operator prompts, not a find prompt: an un-targeted `tumwater prompt` queues
14
- * there. It keeps running through a team-wide `pause` (which stops only the role loops);
13
+ * there. It keeps running through a team-wide `pause` (which stops only the roles);
15
14
  * an un-targeted `stop` signals the whole orchestrator, director included. */
16
15
  export const DIRECTOR_ROLE = "director";
17
- /** The bugfix loop's id. It is the one work role that also defers like a maintenance role while its
18
- * backlog (BUGS.md `## Open`) is empty — see deferTick in src/scheduling/scheduling.ts. With open
16
+ /** The bugfix role's id. It is the one work role that also defers like a maintenance role while its
17
+ * backlog (BUGS.md `## Open`) is empty — see deferRun in src/scheduling/scheduling.ts. With open
19
18
  * bugs it never defers, but pollRunnerReasons stands it down while all of them are held. */
20
19
  export const BUGFIX_ROLE = "bugfix";
21
20
  /** Work-tier roles (need-based prioritization): they
22
- * ship work — feature and bugfix merge code on main, plan feeds them — so their due ticks are
21
+ * ship work — feature and bugfix merge code on main, plan feeds them — so their due runs are
23
22
  * never deferred and slot allocation always orders them ahead of maintenance. */
24
23
  const WORK_ROLES = new Set(["feature", "bugfix", "plan"]);
25
24
  /** Observer roles (plans/observer-roles.md 1/2): a role whose product is an observation, not a
26
25
  * commit, and for which `no_change` means "checked, all well" rather than "found nothing to do".
27
- * The idle ladder's premise — a loop that keeps finding nothing stops burning model time — does
28
- * not hold for these, so their no_change tick schedules at `minRunIntervalSeconds` and leaves
26
+ * The idle ladder's premise — a role that keeps finding nothing stops burning model time — does
27
+ * not hold for these, so their no_change run schedules at `minRunIntervalSeconds` and leaves
29
28
  * `backoffSeconds` at 0 (src/scheduling/backoff.ts). The error ladder still applies in full, and
30
29
  * they are removed from DEFERRABLE_ROLES because their input (the running product for `qa`, the
31
- * event log for `selfcheck`) is not a function of whether main moved. */
32
- export const OBSERVER_ROLES = new Set(["qa", "selfcheck"]);
30
+ * event log for `selfcheck` and `feedback`) is not a function of whether main moved. */
31
+ export const OBSERVER_ROLES = new Set(["qa", "selfcheck", "feedback"]);
33
32
  /** The built-in maintenance roles that author code on their own cadence — the eight members
34
33
  * DEFERRABLE_ROLES and BASELINE_BLOCKED_ROLES share. Both sets list them because both charter
35
34
  * the same roles (defer-until-needed scheduling, red-main blocking), so the list lives once
@@ -45,8 +44,8 @@ const CODE_MAINTENANCE_ROLES = [
45
44
  "improve",
46
45
  ];
47
46
  /** Maintenance-tier roles (need-based prioritization): exactly the ten built-ins whose due
48
- * ticks are deferrable while no feature, bugfix, director, operator or human commit has merged
49
- * into main since their last tick and that tick did nothing. Unknown/custom roles are
47
+ * runs are deferrable while no feature, bugfix, director, operator or human commit has merged
48
+ * into main since their last run and that run did nothing. Unknown/custom roles are
50
49
  * deliberately NOT in this
51
50
  * set — the harness cannot judge what an arbitrary custom role needs, so they never defer (they
52
51
  * still sort into tier 1 for fairOrder via roleTier). Observers are excluded: an unmoved tree
@@ -56,12 +55,12 @@ export const DEFERRABLE_ROLES = new Set([
56
55
  ...CODE_MAINTENANCE_ROLES,
57
56
  "steward",
58
57
  ]);
59
- /** Does this role's recent yield scale its min-tick gap (yield-scaled clocks, PLANS.md)?
58
+ /** Does this role's recent yield scale its min-run gap (yield-scaled clocks, PLANS.md)?
60
59
  * The search/maintenance roles — the deferrable ten, the observers, and bugfix on its
61
60
  * empty-backlog search duty (it is the one work role that defers like maintenance, so its
62
- * idle clock stretches like one too; the open-bugs state is deferTick's concern, not the
63
- * clock's: ten consecutive empty ticks are empty-yield evidence however many bugs are
64
- * recorded). Never the work roles whose ticks follow demand — feature, plan, director — a
61
+ * idle clock stretches like one too; the open-bugs state is deferRun's concern, not the
62
+ * clock's: ten consecutive empty runs are empty-yield evidence however many bugs are
63
+ * recorded). Never the work roles whose runs follow demand — feature, plan, director — a
65
64
  * role with a queued prompt or a fresh wake bypasses the gap entirely (isEligible), and the
66
65
  * multiplier is computed from counted results only (backoff.ts), so feature's errors
67
66
  * and quiet kills never stretch anything. */
@@ -76,24 +75,25 @@ export function roleTier(role) {
76
75
  return WORK_ROLES.has(baseRoleOf(role)) ? 0 : 1;
77
76
  }
78
77
  /** The roles blocked from starting an authoring run while main's own build/test suite is known
79
- * red (the red-main baseline check): every role whose diff
78
+ * red with no failing test named (the red-main baseline check; a red that names its failing tests
79
+ * blocks nothing, and these roles get a `<main-known-red>` note instead): every role whose diff
80
80
  * can carry non-exempt (code) changes — on a red main such a diff is rejected deterministically
81
81
  * by the gate's pre-check, so spending an authoring run on it is pure waste. Exempt: director
82
82
  * (human prompts outrank autonomous gates, like the budget gate), bugfix (the designated healer
83
- * — its tick runs the suite per "leave the project working" and can fix a red main through the
83
+ * — its run executes the suite per "leave the project working" and can fix a red main through the
84
84
  * existing pre-merge gate; blocking it would leave only humans able to unblock the team), and
85
- * plan/readme/steward/qa/selfcheck (markdown-only charter — their diffs are exempt from the build
86
- * pre-check via review.exemptPaths, so a red main does not block them). */
85
+ * plan/readme/steward/qa/selfcheck/feedback (markdown-only charter — their diffs are exempt
86
+ * from the build pre-check via review.exemptPaths, so a red main does not block them). */
87
87
  export const BASELINE_BLOCKED_ROLES = new Set([
88
88
  "feature",
89
89
  ...CODE_MAINTENANCE_ROLES,
90
90
  ]);
91
- /** Is this loop id blocked from authoring on a red main? True when its base role is in
91
+ /** Is this role id blocked from authoring on a red main? True when its base role is in
92
92
  * BASELINE_BLOCKED_ROLES, so `feature-2` is blocked exactly as `feature` is. */
93
93
  export function baselineBlocked(role) {
94
94
  return BASELINE_BLOCKED_ROLES.has(baseRoleOf(role));
95
95
  }
96
- /** Work ratio 4/4: which commit tier a loop's base role merges into, for the work vs maintenance
96
+ /** Work ratio 4/4: which commit tier a role's base role merges into, for the work vs maintenance
97
97
  * split the report and the "Merged today" tile show and the maintenance allowance (Work ratio
98
98
  * 1a/4, src/gates/maintenance-quota.ts) counts. The one home of the split. `work` is the
99
99
  * shipping tier (feature, bugfix, director); `maintenance` is code upkeep plus readme. Every
@@ -121,17 +121,17 @@ export function roleById(id) {
121
121
  * a did-you-mean suggestion when the typed id sits within edit distance 2 of a valid one —
122
122
  * the same treatment unknownConfigKeyError gives config keys, so a mistyped `--role feautre`
123
123
  * names its fix instead of only the valid list. parseRoleFlag, the operator commands, and
124
- * tick-prompt's defensive runner path share it so the wording cannot drift between the CLI,
125
- * the GUI, and a tick's internal error. The hint is text/suggest.ts's typoSuffix —
124
+ * run-prompt's defensive runner path share it so the wording cannot drift between the CLI,
125
+ * the GUI, and a run's internal error. The hint is text/suggest.ts's typoSuffix —
126
126
  * suggestClosest plus didYouMean composed once, the pairing every other unknown-X error
127
127
  * renders through. */
128
128
  export function unknownRoleMessage(role, validIds) {
129
129
  return `unknown role: ${role} (valid ids: ${validIds.join(", ")})${typoSuffix(role, validIds)}`;
130
130
  }
131
- /** A user-defined loop as a Role (plans/user-defined-loops.md): its task IS the
132
- * role-specific find-something-to-do text, and the title is what identifies the loop inside
133
- * its own prompt (`You are the "<name>" loop (user-defined loop)`) and commit context.
134
- * Custom loops run the `default` tier — the harness cannot judge what an arbitrary loop's
131
+ /** A user-defined role as a Role (plans/user-defined-loops.md): its task IS the
132
+ * role-specific find-something-to-do text, and the title is what identifies the role inside
133
+ * its own prompt (`You are the "<name>" role (user-defined role)`) and commit context.
134
+ * Custom roles run the `default` tier — the harness cannot judge what an arbitrary role's
135
135
  * work needs, so it gets the team's ordinary model. */
136
136
  export function customRole(name, task) {
137
137
  return { id: name, title: "custom role", find: task, tier: "default" };
@@ -0,0 +1,97 @@
1
+ /** Runner-id arithmetic for parallel work instances (plans/parallel-work-instances.md, part 1/7). A
2
+ * base role may run several runner ids — `feature`, `feature-2`, `bugfix-3`, … — each keying
3
+ * its own persisted state, branch, refs, session and notes, while every lookup that is ABOUT the
4
+ * role (its catalog charter, model, tier, caps, scheduling class) resolves through the base role.
5
+ * With no instances configured, every runner id is bare, so `baseRoleOf` is the identity.
6
+ *
7
+ * Only `feature` and `bugfix` may gain instances; any other id is returned unchanged, including a
8
+ * custom role whose name merely resembles an instance. */
9
+ /** The base roles that may run more than one instance. */
10
+ export const INSTANCE_ROLES = new Set(["feature", "bugfix"]);
11
+ /** How many runner ids an instance role may hold per configured instance. `instances: N` limits the
12
+ * runs writing code at once; a runner whose change waits in the merge pipeline never runs
13
+ * (plans/merge-queue.md invariant 3), so tumwater keeps up to N × this many runner ids and the
14
+ * poll admits another while fewer than N are running (plans/fast-merge.md "Lever 1"). */
15
+ const RUNNERS_PER_INSTANCE = 2;
16
+ /** The instance-id shape: a base role, a dash, and an index of 2 or more (`-1` is not an
17
+ * instance — the bare id already means instance 1). The source string is also interpolated into
18
+ * the dashboard script (gui-client-labels.ts). */
19
+ export const INSTANCE_ID_SOURCE = "^(feature|bugfix)-([2-9]|[1-9][0-9]+)$";
20
+ const INSTANCE_RE = new RegExp(INSTANCE_ID_SOURCE);
21
+ /** The base role a runner id belongs to: `feature-2` → `feature`, `bugfix-10` → `bugfix`; any
22
+ * other id — a bare role, a custom role, `feature-1` — is returned unchanged. */
23
+ export function baseRoleOf(id) {
24
+ const m = INSTANCE_RE.exec(id);
25
+ return m ? m[1] : id;
26
+ }
27
+ /** The 1-based instance number of a runner id: 1 for a bare role or any non-instance id, N for
28
+ * `<role>-N`. */
29
+ export function instanceIndex(id) {
30
+ const m = INSTANCE_RE.exec(id);
31
+ return m ? Number(m[2]) : 1;
32
+ }
33
+ /** Whether a runner id is listed in a role scope, directly or through its base role — a scope
34
+ * naming `feature` takes `feature-2` too. One home for the instance-aware membership test
35
+ * shared by `logs --role`'s and the merge queue's filters and status snapshot scoping, so the
36
+ * three cannot drift on whether a bare role id takes its instances. */
37
+ export function roleInScope(id, scope) {
38
+ return scope.includes(id) || scope.includes(baseRoleOf(id));
39
+ }
40
+ /** The instance count configured for a runner id's base role (`roles.<base>.instances`, default
41
+ * 1). A caller tells a multi-instance base (whose unassigned runs stage claims for their
42
+ * siblings) from a single-runner one by this. */
43
+ export function configuredInstances(config, id) {
44
+ const role = config.roles[baseRoleOf(id)];
45
+ return role?.instances ?? 1;
46
+ }
47
+ /** How many runner ids a base role holds: its configured instance count, times
48
+ * RUNNERS_PER_INSTANCE once it runs more than one instance. A role left at one instance keeps its
49
+ * single bare runner, so its scheduling is exactly the single-runner one. */
50
+ function runnerCount(config, base) {
51
+ const count = config.roles[base]?.instances ?? 1;
52
+ return INSTANCE_ROLES.has(base) && count > 1 ? count * RUNNERS_PER_INSTANCE : count;
53
+ }
54
+ /** Is this runner id enabled? True when its base role is enabled in config and the id's instance
55
+ * index is within the role's runner count (runnerCount). */
56
+ export function runnerEnabled(config, id) {
57
+ const role = config.roles[baseRoleOf(id)];
58
+ if (role?.enabled !== true)
59
+ return false;
60
+ return instanceIndex(id) <= runnerCount(config, baseRoleOf(id));
61
+ }
62
+ /** Every runner id the config says should exist, in `config.roles` order: each enabled role's
63
+ * bare id, then `<id>-2`…`<id>-M` for the `INSTANCE_ROLES` at their runner count
64
+ * (runnerCount: twice the configured instances once there is more than one). Other roles and
65
+ * custom roles stay bare — only feature and bugfix may split. The one
66
+ * answer to "which runners should exist", so the orchestrator (part 5b/7) and live reload
67
+ * enumerate the same set. At the default instance count this returns exactly the bare ids
68
+ * `enabledRoleIds` returns. */
69
+ export function runnerIds(config) {
70
+ const ids = [];
71
+ for (const [id, role] of Object.entries(config.roles)) {
72
+ if (!role.enabled)
73
+ continue;
74
+ ids.push(id);
75
+ if (!INSTANCE_ROLES.has(id))
76
+ continue;
77
+ const count = runnerCount(config, id);
78
+ for (let i = 2; i <= count; i++)
79
+ ids.push(`${id}-${i}`);
80
+ }
81
+ return ids;
82
+ }
83
+ /** The pause-marker key for a runner id. Instance 1's runner id is the bare role id, which as a
84
+ * pause key would mean the whole role, so while the role runs several instances instance 1 pauses
85
+ * under `<role>-1`. `-1` is a pause key only, never a runner id. Every other id is its own key. */
86
+ export function instancePauseKey(config, runnerId) {
87
+ return INSTANCE_ROLES.has(runnerId) && configuredInstances(config, runnerId) > 1
88
+ ? `${runnerId}-1`
89
+ : runnerId;
90
+ }
91
+ /** Does the per-role pause marker stop this runner id? True for its own id, its base role's id (a
92
+ * whole-role pause), or — for the bare id of an instance role — the `<role>-1` pause key. */
93
+ export function runnerPaused(paused, runnerId) {
94
+ return (paused.has(runnerId) ||
95
+ paused.has(baseRoleOf(runnerId)) ||
96
+ (INSTANCE_ROLES.has(runnerId) && paused.has(`${runnerId}-1`)));
97
+ }
@@ -0,0 +1,119 @@
1
+ /** The pre-queue self-check's acceptance-grep runner: a finished plan entry that says a `grep …`
2
+ * "finds nothing" is checked by running that grep in the worktree, so a change is not rejected
3
+ * for an acceptance grep that still matches. Only the strict "matches nothing" form runs;
4
+ * criteria worded "prints only …" need judgement and stay out. Staging-only (stage-check.ts). */
5
+ import { execFileAsync } from "../process/process.js";
6
+ import { moreSuffix } from "../text/phrases.js";
7
+ import { collapseWhitespace, truncate } from "../text/text.js";
8
+ const MAX_CRITERIA = 5;
9
+ const GREP_TIMEOUT_MS = 20_000;
10
+ const EMPTY_PHRASE_RE = new RegExp("^\\s*(?:(?:finds|prints|returns|outputs|matches|lists) nothing|is empty|lists none|" +
11
+ "(?:prints|finds|lists|returns|gives|shows) no (?:lines?|hits?|matches?|output))", "i");
12
+ const ALLOWANCE_RE = /apart from|except|excluding|other than|besides|beyond/i;
13
+ /** The backticked `grep …`/`git grep …` commands in `body` that the text says match nothing
14
+ * ("finds nothing", "is empty", …), without duplicates, in body order, at most five. A criterion
15
+ * whose sentence (up to the next `.` or `;`) allows some hits ("apart from", "except", …) is
16
+ * dropped. */
17
+ export function emptyGrepCriteria(body) {
18
+ const text = collapseWhitespace(body);
19
+ const found = [];
20
+ for (const m of text.matchAll(/`([^`]+)`/g)) {
21
+ const cmd = m[1] ?? "";
22
+ if (!/^(?:git )?grep /.test(cmd))
23
+ continue;
24
+ const rest = text.slice((m.index ?? 0) + m[0].length);
25
+ const phrase = EMPTY_PHRASE_RE.exec(rest);
26
+ if (phrase === null)
27
+ continue;
28
+ const sentence = rest.slice(0, rest.search(/[.;]/) === -1 ? undefined : rest.search(/[.;]/));
29
+ if (ALLOWANCE_RE.test(sentence))
30
+ continue;
31
+ if (!found.includes(cmd))
32
+ found.push(cmd);
33
+ if (found.length >= MAX_CRITERIA)
34
+ break;
35
+ }
36
+ return found;
37
+ }
38
+ /** A minimal shell-word split of a `grep …`/`git grep …` command, or undefined when it uses
39
+ * shell syntax (an unquoted `|;&<>$()` or backtick) or leaves a quote open. Nothing runs through
40
+ * a shell, so `*` stays literal. */
41
+ export function grepArgv(cmd) {
42
+ const words = [];
43
+ let cur = "";
44
+ let inWord = false;
45
+ let quote = null;
46
+ for (let i = 0; i < cmd.length; i++) {
47
+ const c = cmd[i];
48
+ if (quote === "'") {
49
+ if (c === "'")
50
+ quote = null;
51
+ else
52
+ cur += c;
53
+ }
54
+ else if (quote === '"') {
55
+ const next = cmd[i + 1];
56
+ if (c === "\\" && (next === '"' || next === "\\")) {
57
+ cur += next;
58
+ i++;
59
+ }
60
+ else if (c === '"')
61
+ quote = null;
62
+ else
63
+ cur += c;
64
+ }
65
+ else if (c === "'" || c === '"') {
66
+ quote = c;
67
+ inWord = true;
68
+ }
69
+ else if (c === "\\") {
70
+ if (i + 1 < cmd.length)
71
+ cur += cmd[++i];
72
+ inWord = true;
73
+ }
74
+ else if (/[|;&<>$()`]/.test(c)) {
75
+ return undefined;
76
+ }
77
+ else if (/\s/.test(c)) {
78
+ if (inWord)
79
+ words.push(cur);
80
+ cur = "";
81
+ inWord = false;
82
+ }
83
+ else {
84
+ cur += c;
85
+ inWord = true;
86
+ }
87
+ }
88
+ if (quote !== null)
89
+ return undefined;
90
+ if (inWord)
91
+ words.push(cur);
92
+ if (words[0] === "git" && words[1] === "grep")
93
+ return { file: "git", args: words.slice(1) };
94
+ if (words[0] === "grep")
95
+ return { file: "grep", args: words.slice(1) };
96
+ return undefined;
97
+ }
98
+ /** Run each entry's matches-nothing greps in `wt`; one finding per entry, for its first grep that
99
+ * still prints lines. An exit of 1 passes; any other failure (exit 2, timeout, spawn error) skips
100
+ * the criterion, since an unrunnable command is not evidence. */
101
+ export async function acceptanceGrepFindings(wt, entries) {
102
+ const findings = [];
103
+ for (const entry of entries) {
104
+ for (const cmd of emptyGrepCriteria(entry.body)) {
105
+ const argv = grepArgv(cmd);
106
+ if (argv === undefined)
107
+ continue;
108
+ const out = await execFileAsync(argv.file, argv.args, { cwd: wt, timeout: GREP_TIMEOUT_MS }).then((r) => r.stdout, () => "");
109
+ const lines = out.split("\n").filter((l) => l !== "");
110
+ if (lines.length === 0)
111
+ continue;
112
+ const shown = lines.slice(0, 5).map((l) => truncate(l, 200)).join("; ");
113
+ findings.push(`PLANS.md's finished entry "${truncate(entry.title, 160)}" says \`${cmd}\` matches nothing, but it prints ` +
114
+ `${lines.length} line(s): ${shown}${moreSuffix(lines.length - 5)} — fix those lines, or correct the claim if the criterion is wrong`);
115
+ break;
116
+ }
117
+ }
118
+ return findings;
119
+ }
@@ -0,0 +1,51 @@
1
+ import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
2
+ import { feedbackFilingsPath } from "../paths.js";
3
+ import { isJsonObject } from "../files/json-object.js";
4
+ import { formatAge } from "../text/datetime.js";
5
+ /** The `feedback` observer's per-install filing ledger (plans/feedback-role.md 2/2). Every
6
+ * `feedback` run starts a fresh pi session, so the only durable record of which GitHub issues
7
+ * this install already filed must live in harness state — the same cross-run memory qa's
8
+ * flow-coverage ledger provides (plans/observer-roles.md 2/2), and what makes the next run's
9
+ * prompt able to render a "previously filed" block so the charter's dedupe does not rely on the
10
+ * network alone. The ledger is gitignored runtime state under .tumwater/state/ and degrades to
11
+ * "no data" on any missing or malformed file: an observer must never fail a run on bookkeeping
12
+ * (a filing that fails to persist is a warning, never a run failure). */
13
+ /** The newest bound the ledger keeps. A fresh session files at most one issue per run, so this
14
+ * caps the block's growth and the prompt's size; older filings are dropped (the charter still
15
+ * dedupes against the live `gh issue list`). */
16
+ export const FEEDBACK_FILINGS_MAX = 100;
17
+ function isFiling(value) {
18
+ if (!isJsonObject(value))
19
+ return false;
20
+ return (typeof value.issue === "number" &&
21
+ typeof value.title === "string" &&
22
+ typeof value.filedAt === "number");
23
+ }
24
+ /** Read the ledger, keeping only well-formed entries. A missing, torn, or wrong-shaped file
25
+ * reads as an empty ledger — the caller renders no block and the run proceeds as before. */
26
+ export function readFeedbackFilings(root) {
27
+ const file = readJsonFile(feedbackFilingsPath(root));
28
+ const filings = file?.filings;
29
+ if (!Array.isArray(filings))
30
+ return [];
31
+ return filings.filter(isFiling);
32
+ }
33
+ /** Record that this install filed issue `issue` now (or at `now`, for tests). Reads-modifies-
34
+ * writes the whole ledger atomically so a concurrent writer cannot leave a torn file behind,
35
+ * and keeps only the newest FEEDBACK_FILINGS_MAX entries. */
36
+ export function recordFiled(root, issue, title, now = Date.now()) {
37
+ const filings = readFeedbackFilings(root);
38
+ const entry = { issue, title, filedAt: now };
39
+ writeJsonAtomic(feedbackFilingsPath(root), {
40
+ filings: [entry, ...filings].slice(0, FEEDBACK_FILINGS_MAX),
41
+ });
42
+ }
43
+ /** Render the "previously filed" block for the next `feedback` prompt; undefined when the
44
+ * ledger is empty (the common first-run case), so the prompt is unchanged then. Newest-first,
45
+ * each line `#<n> <title> (<age>)` mirroring qa's coverage lines. */
46
+ export function renderFilingsBlock(filings, now = Date.now()) {
47
+ if (filings.length === 0)
48
+ return undefined;
49
+ const lines = filings.map((f) => ` #${f.issue} ${f.title} (${formatAge(now - f.filedAt)})`);
50
+ return ["Previously filed from this install:", ...lines].join("\n");
51
+ }
@@ -1,14 +1,14 @@
1
- // vocabulary: plain
2
1
  import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
3
2
  import { qaCoveragePath } from "../paths.js";
4
- import { isJsonObject } from "../files/json-object.js";
5
- /** The `qa` observer's flow-coverage ledger (plans/observer-roles.md 2/2). Every `qa` tick
3
+ import { isJsonObject, setOwn } from "../files/json-object.js";
4
+ import { formatAge } from "../text/datetime.js";
5
+ /** The `qa` observer's flow-coverage ledger (plans/observer-roles.md 2/2). Every `qa` run
6
6
  * starts a fresh pi session, so the only durable memory of which flow it last exercised is a
7
7
  * runtime file the harness — not pi — maintains. A passing cheap check writes nothing into the
8
- * repo (a note commit per check would move main and wake every sleeping loop), so without this
9
- * ledger the model picks a flow blind every tick and converges on the top of its menu. The
8
+ * repo (a note commit per check would move main and wake every sleeping role), so without this
9
+ * ledger the model picks a flow blind every run and converges on the top of its menu. The
10
10
  * ledger is gitignored runtime state under .tumwater/state/ and degrades to "no data" on any
11
- * missing or malformed file: an observer must never fail a tick on bookkeeping. */
11
+ * missing or malformed file: an observer must never fail a run on bookkeeping. */
12
12
  /** The flows `qa` exercises, cheapest-first — the ordered menu from plans/qa-role.md, plus the
13
13
  * expensive `run (real)` variant. The names are the universe the coverage block renders; a flow
14
14
  * recorded by name but absent here (a project-specific flow) still renders, appended after. */
@@ -26,16 +26,8 @@ export const QA_FLOWS = [
26
26
  function isResult(value) {
27
27
  return value === "passed" || value === "bug";
28
28
  }
29
- /** Store one flow under its name. A plain `coverage[flow] = entry` loses a name of `__proto__`:
30
- * the assignment hits Object.prototype's setter and rewrites the map's prototype instead of
31
- * adding an own entry, so the flow vanishes from every reader and from the persisted JSON. The
32
- * qa model's own FLOW line supplies the name, so it crosses an external boundary; defining the
33
- * own property keeps even that key, matching claude-account's setWindow. */
34
- function setFlow(coverage, flow, entry) {
35
- Object.defineProperty(coverage, flow, { value: entry, enumerable: true, writable: true, configurable: true });
36
- }
37
29
  /** Read the ledger, keeping only well-formed entries. A missing, torn, or wrong-shaped file
38
- * reads as an empty ledger — the caller renders no block and the tick proceeds as before. */
30
+ * reads as an empty ledger — the caller renders no block and the run proceeds as before. */
39
31
  export function readQaCoverage(root) {
40
32
  const file = readJsonFile(qaCoveragePath(root));
41
33
  const flows = file?.flows;
@@ -48,7 +40,7 @@ export function readQaCoverage(root) {
48
40
  const entry = raw;
49
41
  if (typeof entry.lastRunAt !== "number" || !isResult(entry.result))
50
42
  continue;
51
- setFlow(out, name, {
43
+ setOwn(out, name, {
52
44
  lastRunAt: entry.lastRunAt,
53
45
  result: entry.result,
54
46
  ...(typeof entry.summary === "string" ? { summary: entry.summary } : {}),
@@ -60,19 +52,9 @@ export function readQaCoverage(root) {
60
52
  * whole ledger atomically so a concurrent writer cannot leave a torn file behind. */
61
53
  export function recordFlow(root, flow, result, summary, now = Date.now()) {
62
54
  const coverage = readQaCoverage(root);
63
- setFlow(coverage, flow, { lastRunAt: now, result, ...(summary ? { summary } : {}) });
55
+ setOwn(coverage, flow, { lastRunAt: now, result, ...(summary ? { summary } : {}) });
64
56
  writeJsonAtomic(qaCoveragePath(root), { flows: coverage });
65
57
  }
66
- /** A short age: minutes under an hour, hours under a day, days beyond (`12m`, `4h`, `6d`). */
67
- function formatAge(ms) {
68
- const minutes = Math.max(0, Math.floor(ms / 60_000));
69
- if (minutes < 60)
70
- return `${minutes}m`;
71
- const hours = Math.floor(minutes / 60);
72
- if (hours < 24)
73
- return `${hours}h`;
74
- return `${Math.floor(hours / 24)}d`;
75
- }
76
58
  /** Render the coverage block for the next `qa` prompt. Never-exercised flows lead (age = ∞),
77
59
  * then exercised flows oldest-first, so "exercise the flow at the top" actually rotates through
78
60
  * the whole menu instead of re-picking whatever is cheapest. The flow universe is `QA_FLOWS`