@quickchatai/quickreview 0.0.0-stage → 1.1.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 (1443) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +385 -2
  3. package/THIRD-PARTY-NOTICES.md +134 -0
  4. package/agents/pass-abstraction.md +64 -0
  5. package/agents/pass-ask-human.md +134 -0
  6. package/agents/pass-backcompat.md +67 -0
  7. package/agents/pass-baseline.md +61 -0
  8. package/agents/pass-behaviour-delta.md +64 -0
  9. package/agents/pass-bounds.md +36 -0
  10. package/agents/pass-claims.md +31 -0
  11. package/agents/pass-concurrency.md +168 -0
  12. package/agents/pass-consistency.md +57 -0
  13. package/agents/pass-contracts.md +30 -0
  14. package/agents/pass-data-safety.md +54 -0
  15. package/agents/pass-deferred.md +35 -0
  16. package/agents/pass-delivery.md +37 -0
  17. package/agents/pass-duplication.md +60 -0
  18. package/agents/pass-intent.md +63 -0
  19. package/agents/pass-lint-conformance.md +46 -0
  20. package/agents/pass-maintainability.md +67 -0
  21. package/agents/pass-placement.md +68 -0
  22. package/agents/pass-prod-risk.md +24 -0
  23. package/agents/pass-rollout.md +32 -0
  24. package/agents/pass-roundtrip.md +46 -0
  25. package/agents/pass-security.md +31 -0
  26. package/agents/pass-simplicity.md +68 -0
  27. package/agents/pass-statelessness.md +27 -0
  28. package/agents/pass-style.md +37 -0
  29. package/agents/pass-sweep.md +57 -0
  30. package/agents/pass-tests.md +58 -0
  31. package/agents/pass-ui.md +23 -0
  32. package/agents/pass-validate.md +50 -0
  33. package/agents/pass-widened.md +46 -0
  34. package/agents/scan/pass-scan.md +86 -0
  35. package/bin/qcr.js +11 -0
  36. package/config.example.toml +1067 -0
  37. package/deploy/ansible/README.md +356 -0
  38. package/deploy/ansible/group_vars/all.yml +212 -0
  39. package/deploy/ansible/inventory.example.ini +25 -0
  40. package/deploy/ansible/playbook.yml +360 -0
  41. package/deploy/collect-secrets.sh +289 -0
  42. package/deploy/collect-slack-secrets.mjs +1190 -0
  43. package/deploy/github-app-manifest.json +31 -0
  44. package/deploy/manifest.schema.json +75 -0
  45. package/deploy/qcr-update.sh +803 -0
  46. package/deploy/reviewer/AGENTS.override.md +12 -0
  47. package/dist/extensions/cli/bash-guard-ambient.js +42 -0
  48. package/dist/extensions/cli/bash-guard-ambient.js.map +1 -0
  49. package/dist/extensions/cli/bash-guard.js +5 -0
  50. package/dist/extensions/cli/bash-guard.js.map +1 -0
  51. package/dist/extensions/cli/companion-guard.js +5 -0
  52. package/dist/extensions/cli/companion-guard.js.map +1 -0
  53. package/dist/extensions/cli/debug-round.js +8 -0
  54. package/dist/extensions/cli/debug-round.js.map +1 -0
  55. package/dist/extensions/cli/inject-bridge.js +5 -0
  56. package/dist/extensions/cli/inject-bridge.js.map +1 -0
  57. package/dist/extensions/cli/ledger-pin.js +5 -0
  58. package/dist/extensions/cli/ledger-pin.js.map +1 -0
  59. package/dist/extensions/cli/lens.js +13 -0
  60. package/dist/extensions/cli/lens.js.map +1 -0
  61. package/dist/extensions/cli/muted-theme.js +5 -0
  62. package/dist/extensions/cli/muted-theme.js.map +1 -0
  63. package/dist/extensions/cli/onboarding.js +5 -0
  64. package/dist/extensions/cli/onboarding.js.map +1 -0
  65. package/dist/extensions/cli/setup.js +5 -0
  66. package/dist/extensions/cli/setup.js.map +1 -0
  67. package/dist/extensions/cli/tui-view.js +5 -0
  68. package/dist/extensions/cli/tui-view.js.map +1 -0
  69. package/dist/extensions/companion-guard.js +361 -0
  70. package/dist/extensions/companion-guard.js.map +1 -0
  71. package/dist/extensions/debug-round.js +204 -0
  72. package/dist/extensions/debug-round.js.map +1 -0
  73. package/dist/extensions/inject-bridge.js +603 -0
  74. package/dist/extensions/inject-bridge.js.map +1 -0
  75. package/dist/extensions/muted-theme.js +12 -0
  76. package/dist/extensions/muted-theme.js.map +1 -0
  77. package/dist/extensions/onboarding.js +175 -0
  78. package/dist/extensions/onboarding.js.map +1 -0
  79. package/dist/extensions/setup-replay.js +269 -0
  80. package/dist/extensions/setup-replay.js.map +1 -0
  81. package/dist/extensions/setup-scan.js +998 -0
  82. package/dist/extensions/setup-scan.js.map +1 -0
  83. package/dist/extensions/setup-triage.js +175 -0
  84. package/dist/extensions/setup-triage.js.map +1 -0
  85. package/dist/extensions/setup.js +1160 -0
  86. package/dist/extensions/setup.js.map +1 -0
  87. package/dist/extensions/tui-view.js +389 -0
  88. package/dist/extensions/tui-view.js.map +1 -0
  89. package/dist/extensions/vendor/openviking-pi/client.js +376 -0
  90. package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
  91. package/dist/extensions/vendor/openviking-pi/config.js +189 -0
  92. package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
  93. package/dist/extensions/vendor/openviking-pi/index.js +421 -0
  94. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
  95. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
  96. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
  97. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
  98. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
  99. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
  100. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
  101. package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
  102. package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
  103. package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
  104. package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
  105. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
  106. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
  107. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
  108. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
  109. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
  110. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
  111. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
  112. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
  113. package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
  114. package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
  115. package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
  116. package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
  117. package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
  118. package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
  119. package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
  120. package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
  121. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
  122. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
  123. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
  124. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
  125. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
  126. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
  127. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
  128. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
  129. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
  130. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
  131. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
  132. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
  133. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
  134. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
  135. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
  136. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
  137. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
  138. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
  139. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  140. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
  141. package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
  142. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
  143. package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
  144. package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
  145. package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
  146. package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
  147. package/dist/src/anomalies.js +483 -0
  148. package/dist/src/anomalies.js.map +1 -0
  149. package/dist/src/answerworker.js +180 -0
  150. package/dist/src/answerworker.js.map +1 -0
  151. package/dist/src/appstamp.js +68 -0
  152. package/dist/src/appstamp.js.map +1 -0
  153. package/dist/src/archive/admission.js +3 -0
  154. package/dist/src/archive/admission.js.map +1 -0
  155. package/dist/src/archive/archivedb.js +474 -0
  156. package/dist/src/archive/archivedb.js.map +1 -0
  157. package/dist/src/archive/archiver.js +1770 -0
  158. package/dist/src/archive/archiver.js.map +1 -0
  159. package/dist/src/archive/archivewrite.js +198 -0
  160. package/dist/src/archive/archivewrite.js.map +1 -0
  161. package/dist/src/archive/bundle.js +266 -0
  162. package/dist/src/archive/bundle.js.map +1 -0
  163. package/dist/src/archive/dbmove.js +638 -0
  164. package/dist/src/archive/dbmove.js.map +1 -0
  165. package/dist/src/archive/duplicatecounts.js +80 -0
  166. package/dist/src/archive/duplicatecounts.js.map +1 -0
  167. package/dist/src/archive/eligibility.js +113 -0
  168. package/dist/src/archive/eligibility.js.map +1 -0
  169. package/dist/src/archive/extract.js +288 -0
  170. package/dist/src/archive/extract.js.map +1 -0
  171. package/dist/src/archive/fsutil.js +330 -0
  172. package/dist/src/archive/fsutil.js.map +1 -0
  173. package/dist/src/archive/gzfile.js +277 -0
  174. package/dist/src/archive/gzfile.js.map +1 -0
  175. package/dist/src/archive/kinds.js +22 -0
  176. package/dist/src/archive/kinds.js.map +1 -0
  177. package/dist/src/archive/livecheckpoint.js +178 -0
  178. package/dist/src/archive/livecheckpoint.js.map +1 -0
  179. package/dist/src/archive/liveretention.js +145 -0
  180. package/dist/src/archive/liveretention.js.map +1 -0
  181. package/dist/src/archive/manifest.js +284 -0
  182. package/dist/src/archive/manifest.js.map +1 -0
  183. package/dist/src/archive/overseer.js +283 -0
  184. package/dist/src/archive/overseer.js.map +1 -0
  185. package/dist/src/archive/piglobal.js +129 -0
  186. package/dist/src/archive/piglobal.js.map +1 -0
  187. package/dist/src/archive/plan.js +398 -0
  188. package/dist/src/archive/plan.js.map +1 -0
  189. package/dist/src/archive/prarchive.js +831 -0
  190. package/dist/src/archive/prarchive.js.map +1 -0
  191. package/dist/src/archive/prfiles.js +303 -0
  192. package/dist/src/archive/prfiles.js.map +1 -0
  193. package/dist/src/archive/report.js +169 -0
  194. package/dist/src/archive/report.js.map +1 -0
  195. package/dist/src/archive/restore.js +276 -0
  196. package/dist/src/archive/restore.js.map +1 -0
  197. package/dist/src/archive/rows.js +106 -0
  198. package/dist/src/archive/rows.js.map +1 -0
  199. package/dist/src/archive/seal.js +480 -0
  200. package/dist/src/archive/seal.js.map +1 -0
  201. package/dist/src/archive/status.js +528 -0
  202. package/dist/src/archive/status.js.map +1 -0
  203. package/dist/src/archive/storepass.js +409 -0
  204. package/dist/src/archive/storepass.js.map +1 -0
  205. package/dist/src/backfill.js +217 -0
  206. package/dist/src/backfill.js.map +1 -0
  207. package/dist/src/bench/cost.js +87 -0
  208. package/dist/src/bench/exec.js +76 -0
  209. package/dist/src/bench/values.js +14 -0
  210. package/dist/src/botcontrol.js +127 -0
  211. package/dist/src/botcontrol.js.map +1 -0
  212. package/dist/src/botworker.js +805 -0
  213. package/dist/src/botworker.js.map +1 -0
  214. package/dist/src/capture.js +234 -0
  215. package/dist/src/capture.js.map +1 -0
  216. package/dist/src/cases.js +1491 -0
  217. package/dist/src/cases.js.map +1 -0
  218. package/dist/src/channelfs.js +27 -0
  219. package/dist/src/channelfs.js.map +1 -0
  220. package/dist/src/checklist.js +312 -0
  221. package/dist/src/checklist.js.map +1 -0
  222. package/dist/src/ciretry.js +91 -0
  223. package/dist/src/ciretry.js.map +1 -0
  224. package/dist/src/cli.js +1916 -0
  225. package/dist/src/cli.js.map +1 -0
  226. package/dist/src/cliops.js +333 -0
  227. package/dist/src/cliops.js.map +1 -0
  228. package/dist/src/clonedir.js +15 -0
  229. package/dist/src/clonedir.js.map +1 -0
  230. package/dist/src/commandeffects.js +43 -0
  231. package/dist/src/commandeffects.js.map +1 -0
  232. package/dist/src/commands.js +1312 -0
  233. package/dist/src/commands.js.map +1 -0
  234. package/dist/src/companioninbox.js +203 -0
  235. package/dist/src/companioninbox.js.map +1 -0
  236. package/dist/src/compare.js +673 -0
  237. package/dist/src/compare.js.map +1 -0
  238. package/dist/src/config.js +1027 -0
  239. package/dist/src/config.js.map +1 -0
  240. package/dist/src/configcheck.js +111 -0
  241. package/dist/src/configcheck.js.map +1 -0
  242. package/dist/src/coreext.js +43 -0
  243. package/dist/src/coreext.js.map +1 -0
  244. package/dist/src/daemon.js +929 -0
  245. package/dist/src/daemon.js.map +1 -0
  246. package/dist/src/debuground.js +731 -0
  247. package/dist/src/debuground.js.map +1 -0
  248. package/dist/src/debugroundpi.js +219 -0
  249. package/dist/src/debugroundpi.js.map +1 -0
  250. package/dist/src/diagnostics.js +14 -0
  251. package/dist/src/diagnostics.js.map +1 -0
  252. package/dist/src/digest.js +139 -0
  253. package/dist/src/digest.js.map +1 -0
  254. package/dist/src/dispatch.js +1726 -0
  255. package/dist/src/dispatch.js.map +1 -0
  256. package/dist/src/engine/answerguard.js +18 -0
  257. package/dist/src/engine/answerguard.js.map +1 -0
  258. package/dist/src/engine/botsdk.js +262 -0
  259. package/dist/src/engine/botsdk.js.map +1 -0
  260. package/dist/src/engine/caserunner-sdk.js +332 -0
  261. package/dist/src/engine/caserunner-sdk.js.map +1 -0
  262. package/dist/src/engine/debugpi.js +194 -0
  263. package/dist/src/engine/debugpi.js.map +1 -0
  264. package/dist/src/eod.js +473 -0
  265. package/dist/src/eod.js.map +1 -0
  266. package/dist/src/eodpinwatch.js +391 -0
  267. package/dist/src/eodpinwatch.js.map +1 -0
  268. package/dist/src/escalation.js +1009 -0
  269. package/dist/src/escalation.js.map +1 -0
  270. package/dist/src/eventloop.js +779 -0
  271. package/dist/src/eventloop.js.map +1 -0
  272. package/dist/src/execboundary.js +430 -0
  273. package/dist/src/execboundary.js.map +1 -0
  274. package/dist/src/feedback.js +915 -0
  275. package/dist/src/feedback.js.map +1 -0
  276. package/dist/src/findingscheck.js +200 -0
  277. package/dist/src/findingscheck.js.map +1 -0
  278. package/dist/src/fsdenied.js +26 -0
  279. package/dist/src/fsdenied.js.map +1 -0
  280. package/dist/src/ghread.js +181 -0
  281. package/dist/src/ghread.js.map +1 -0
  282. package/dist/src/github.js +1063 -0
  283. package/dist/src/github.js.map +1 -0
  284. package/dist/src/homes.js +103 -0
  285. package/dist/src/homes.js.map +1 -0
  286. package/dist/src/hostroute.js +24 -0
  287. package/dist/src/hostroute.js.map +1 -0
  288. package/dist/src/improvements.js +625 -0
  289. package/dist/src/improvements.js.map +1 -0
  290. package/dist/src/install.js +946 -0
  291. package/dist/src/install.js.map +1 -0
  292. package/dist/src/installkind.js +40 -0
  293. package/dist/src/installkind.js.map +1 -0
  294. package/dist/src/ipc/capability.js +286 -0
  295. package/dist/src/ipc/capability.js.map +1 -0
  296. package/dist/src/ipc/client.js +226 -0
  297. package/dist/src/ipc/client.js.map +1 -0
  298. package/dist/src/ipc/control.js +70 -0
  299. package/dist/src/ipc/control.js.map +1 -0
  300. package/dist/src/ipc/ghreadshim.js +77 -0
  301. package/dist/src/ipc/ghreadshim.js.map +1 -0
  302. package/dist/src/ipc/launcher.js +65 -0
  303. package/dist/src/ipc/launcher.js.map +1 -0
  304. package/dist/src/ipc/permits.js +293 -0
  305. package/dist/src/ipc/permits.js.map +1 -0
  306. package/dist/src/ipc/postshim.js +137 -0
  307. package/dist/src/ipc/postshim.js.map +1 -0
  308. package/dist/src/ipc/protocol.js +239 -0
  309. package/dist/src/ipc/protocol.js.map +1 -0
  310. package/dist/src/ipc/server.js +312 -0
  311. package/dist/src/ipc/server.js.map +1 -0
  312. package/dist/src/ipc/workerspec.js +148 -0
  313. package/dist/src/ipc/workerspec.js.map +1 -0
  314. package/dist/src/learner/learner.js +1272 -0
  315. package/dist/src/learner/learner.js.map +1 -0
  316. package/dist/src/learner/miner.js +948 -0
  317. package/dist/src/learner/miner.js.map +1 -0
  318. package/dist/src/learner/packet.js +216 -0
  319. package/dist/src/learner/packet.js.map +1 -0
  320. package/dist/src/learner/receipt.js +135 -0
  321. package/dist/src/learner/receipt.js.map +1 -0
  322. package/dist/src/learner/status.js +38 -0
  323. package/dist/src/learner/status.js.map +1 -0
  324. package/dist/src/ledgercheck.js +120 -0
  325. package/dist/src/ledgercheck.js.map +1 -0
  326. package/dist/src/lessonledger.js +451 -0
  327. package/dist/src/lessonledger.js.map +1 -0
  328. package/dist/src/lessonselect.js +639 -0
  329. package/dist/src/lessonselect.js.map +1 -0
  330. package/dist/src/lessonshadow.js +540 -0
  331. package/dist/src/lessonshadow.js.map +1 -0
  332. package/dist/src/levers.js +302 -0
  333. package/dist/src/levers.js.map +1 -0
  334. package/dist/src/live/projection.js +450 -0
  335. package/dist/src/live/projection.js.map +1 -0
  336. package/dist/src/live/publish.js +83 -0
  337. package/dist/src/live/publish.js.map +1 -0
  338. package/dist/src/live/read.js +60 -0
  339. package/dist/src/live/read.js.map +1 -0
  340. package/dist/src/livecli.js +288 -0
  341. package/dist/src/livecli.js.map +1 -0
  342. package/dist/src/loginhint.js +42 -0
  343. package/dist/src/loginhint.js.map +1 -0
  344. package/dist/src/loginrefresh/atomic.js +66 -0
  345. package/dist/src/loginrefresh/atomic.js.map +1 -0
  346. package/dist/src/loginrefresh/lock.js +93 -0
  347. package/dist/src/loginrefresh/lock.js.map +1 -0
  348. package/dist/src/loginrefresh/main.js +58 -0
  349. package/dist/src/loginrefresh/main.js.map +1 -0
  350. package/dist/src/loginrefresh/pirefresh.js +49 -0
  351. package/dist/src/loginrefresh/pirefresh.js.map +1 -0
  352. package/dist/src/loginrefresh/refresher.js +286 -0
  353. package/dist/src/loginrefresh/refresher.js.map +1 -0
  354. package/dist/src/loginrefresh/supervisor.js +253 -0
  355. package/dist/src/loginrefresh/supervisor.js.map +1 -0
  356. package/dist/src/loginrefresh/types.js +4 -0
  357. package/dist/src/loginrefresh/types.js.map +1 -0
  358. package/dist/src/memory/context.js +251 -0
  359. package/dist/src/memory/context.js.map +1 -0
  360. package/dist/src/memory/datafence.js +5 -0
  361. package/dist/src/memory/datafence.js.map +1 -0
  362. package/dist/src/memory/delivery.js +131 -0
  363. package/dist/src/memory/delivery.js.map +1 -0
  364. package/dist/src/memory/eventtags.js +81 -0
  365. package/dist/src/memory/eventtags.js.map +1 -0
  366. package/dist/src/memory/health.js +713 -0
  367. package/dist/src/memory/health.js.map +1 -0
  368. package/dist/src/memory/import.js +504 -0
  369. package/dist/src/memory/import.js.map +1 -0
  370. package/dist/src/memory/layout.js +252 -0
  371. package/dist/src/memory/layout.js.map +1 -0
  372. package/dist/src/memory/legacy-file.js +100 -0
  373. package/dist/src/memory/legacy-file.js.map +1 -0
  374. package/dist/src/memory/lessons.js +370 -0
  375. package/dist/src/memory/lessons.js.map +1 -0
  376. package/dist/src/memory/ovsession.js +449 -0
  377. package/dist/src/memory/ovsession.js.map +1 -0
  378. package/dist/src/memory/relayout.js +259 -0
  379. package/dist/src/memory/relayout.js.map +1 -0
  380. package/dist/src/memory/roundactivity.js +180 -0
  381. package/dist/src/memory/roundactivity.js.map +1 -0
  382. package/dist/src/memory/selection.js +690 -0
  383. package/dist/src/memory/selection.js.map +1 -0
  384. package/dist/src/memory/shadowstatus.js +69 -0
  385. package/dist/src/memory/shadowstatus.js.map +1 -0
  386. package/dist/src/memory/store.js +83 -0
  387. package/dist/src/memory/store.js.map +1 -0
  388. package/dist/src/memory/textidentity.js +8 -0
  389. package/dist/src/memory/textidentity.js.map +1 -0
  390. package/dist/src/metrics.js +773 -0
  391. package/dist/src/metrics.js.map +1 -0
  392. package/dist/src/missingconfig.js +8 -0
  393. package/dist/src/missingconfig.js.map +1 -0
  394. package/dist/src/npmverbs.js +226 -0
  395. package/dist/src/npmverbs.js.map +1 -0
  396. package/dist/src/onboard/choices.js +191 -0
  397. package/dist/src/onboard/choices.js.map +1 -0
  398. package/dist/src/onboard/engine.js +344 -0
  399. package/dist/src/onboard/engine.js.map +1 -0
  400. package/dist/src/onboard/launch.js +83 -0
  401. package/dist/src/onboard/launch.js.map +1 -0
  402. package/dist/src/onboard/steps/appslug.js +211 -0
  403. package/dist/src/onboard/steps/appslug.js.map +1 -0
  404. package/dist/src/onboard/steps/bringup.js +408 -0
  405. package/dist/src/onboard/steps/bringup.js.map +1 -0
  406. package/dist/src/onboard/steps/compose.js +343 -0
  407. package/dist/src/onboard/steps/compose.js.map +1 -0
  408. package/dist/src/onboard/steps/configdelivery.js +50 -0
  409. package/dist/src/onboard/steps/configdelivery.js.map +1 -0
  410. package/dist/src/onboard/steps/configfill.js +1148 -0
  411. package/dist/src/onboard/steps/configfill.js.map +1 -0
  412. package/dist/src/onboard/steps/deploysh.js +80 -0
  413. package/dist/src/onboard/steps/deploysh.js.map +1 -0
  414. package/dist/src/onboard/steps/embeddings.js +472 -0
  415. package/dist/src/onboard/steps/embeddings.js.map +1 -0
  416. package/dist/src/onboard/steps/escalation.js +571 -0
  417. package/dist/src/onboard/steps/escalation.js.map +1 -0
  418. package/dist/src/onboard/steps/githubapp.js +1092 -0
  419. package/dist/src/onboard/steps/githubapp.js.map +1 -0
  420. package/dist/src/onboard/steps/golive.js +506 -0
  421. package/dist/src/onboard/steps/golive.js.map +1 -0
  422. package/dist/src/onboard/steps/index.js +67 -0
  423. package/dist/src/onboard/steps/index.js.map +1 -0
  424. package/dist/src/onboard/steps/ingress.js +762 -0
  425. package/dist/src/onboard/steps/ingress.js.map +1 -0
  426. package/dist/src/onboard/steps/inventory.js +420 -0
  427. package/dist/src/onboard/steps/inventory.js.map +1 -0
  428. package/dist/src/onboard/steps/llmauth.js +893 -0
  429. package/dist/src/onboard/steps/llmauth.js.map +1 -0
  430. package/dist/src/onboard/steps/memory.js +1060 -0
  431. package/dist/src/onboard/steps/memory.js.map +1 -0
  432. package/dist/src/onboard/steps/memorykeys.js +43 -0
  433. package/dist/src/onboard/steps/memorykeys.js.map +1 -0
  434. package/dist/src/onboard/steps/preflight.js +172 -0
  435. package/dist/src/onboard/steps/preflight.js.map +1 -0
  436. package/dist/src/onboard/steps/proof.js +502 -0
  437. package/dist/src/onboard/steps/proof.js.map +1 -0
  438. package/dist/src/onboard/steps/ssh.js +300 -0
  439. package/dist/src/onboard/steps/ssh.js.map +1 -0
  440. package/dist/src/onboard/steps/topology.js +291 -0
  441. package/dist/src/onboard/steps/topology.js.map +1 -0
  442. package/dist/src/onboard/steps/transport.js +511 -0
  443. package/dist/src/onboard/steps/transport.js.map +1 -0
  444. package/dist/src/onboard/steps/verifyup.js +362 -0
  445. package/dist/src/onboard/steps/verifyup.js.map +1 -0
  446. package/dist/src/onboard/steps/vmdeploy.js +368 -0
  447. package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
  448. package/dist/src/onboard/toml.js +53 -0
  449. package/dist/src/onboard/toml.js.map +1 -0
  450. package/dist/src/opencli.js +508 -0
  451. package/dist/src/opencli.js.map +1 -0
  452. package/dist/src/openpr.js +457 -0
  453. package/dist/src/openpr.js.map +1 -0
  454. package/dist/src/openviking/client.js +821 -0
  455. package/dist/src/openviking/client.js.map +1 -0
  456. package/dist/src/openviking/context.js +218 -0
  457. package/dist/src/openviking/context.js.map +1 -0
  458. package/dist/src/openviking/embedder.js +457 -0
  459. package/dist/src/openviking/embedder.js.map +1 -0
  460. package/dist/src/openviking/gapsink.js +38 -0
  461. package/dist/src/openviking/gapsink.js.map +1 -0
  462. package/dist/src/openviking/health.js +69 -0
  463. package/dist/src/openviking/health.js.map +1 -0
  464. package/dist/src/openviking/index.js +24 -0
  465. package/dist/src/openviking/index.js.map +1 -0
  466. package/dist/src/openviking/lessonfind.js +54 -0
  467. package/dist/src/openviking/lessonfind.js.map +1 -0
  468. package/dist/src/openviking/probe.js +89 -0
  469. package/dist/src/openviking/probe.js.map +1 -0
  470. package/dist/src/openviking/proc.js +95 -0
  471. package/dist/src/openviking/proc.js.map +1 -0
  472. package/dist/src/openviking/provisionreport.js +144 -0
  473. package/dist/src/openviking/provisionreport.js.map +1 -0
  474. package/dist/src/openviking/reembed.js +317 -0
  475. package/dist/src/openviking/reembed.js.map +1 -0
  476. package/dist/src/openviking/relayout.js +153 -0
  477. package/dist/src/openviking/relayout.js.map +1 -0
  478. package/dist/src/openviking/sidecar-main.js +39 -0
  479. package/dist/src/openviking/sidecar-main.js.map +1 -0
  480. package/dist/src/openviking/sidecar.js +79 -0
  481. package/dist/src/openviking/sidecar.js.map +1 -0
  482. package/dist/src/openviking/store.js +1758 -0
  483. package/dist/src/openviking/store.js.map +1 -0
  484. package/dist/src/openviking/supervisor.js +422 -0
  485. package/dist/src/openviking/supervisor.js.map +1 -0
  486. package/dist/src/openviking/types.js +5 -0
  487. package/dist/src/openviking/types.js.map +1 -0
  488. package/dist/src/operator-recovery.js +172 -0
  489. package/dist/src/operator-recovery.js.map +1 -0
  490. package/dist/src/operatormessage.js +44 -0
  491. package/dist/src/operatormessage.js.map +1 -0
  492. package/dist/src/outcomes.js +1361 -0
  493. package/dist/src/outcomes.js.map +1 -0
  494. package/dist/src/piauthcopy.js +5 -0
  495. package/dist/src/piauthcopy.js.map +1 -0
  496. package/dist/src/picli.js +45 -0
  497. package/dist/src/picli.js.map +1 -0
  498. package/dist/src/picwd.js +25 -0
  499. package/dist/src/picwd.js.map +1 -0
  500. package/dist/src/pilogin.js +35 -0
  501. package/dist/src/pilogin.js.map +1 -0
  502. package/dist/src/pingpong.js +1028 -0
  503. package/dist/src/pingpong.js.map +1 -0
  504. package/dist/src/posting-journal.js +441 -0
  505. package/dist/src/posting-journal.js.map +1 -0
  506. package/dist/src/posting-outbox.js +662 -0
  507. package/dist/src/posting-outbox.js.map +1 -0
  508. package/dist/src/posting-receipt.js +203 -0
  509. package/dist/src/posting-receipt.js.map +1 -0
  510. package/dist/src/posting.js +348 -0
  511. package/dist/src/posting.js.map +1 -0
  512. package/dist/src/postops.js +637 -0
  513. package/dist/src/postops.js.map +1 -0
  514. package/dist/src/pr-activity.js +11 -0
  515. package/dist/src/pr-activity.js.map +1 -0
  516. package/dist/src/pr-author.js +5 -0
  517. package/dist/src/pr-author.js.map +1 -0
  518. package/dist/src/pr-conversation.js +355 -0
  519. package/dist/src/pr-conversation.js.map +1 -0
  520. package/dist/src/pr-session-lifecycle.js +542 -0
  521. package/dist/src/pr-session-lifecycle.js.map +1 -0
  522. package/dist/src/preflight.js +341 -0
  523. package/dist/src/preflight.js.map +1 -0
  524. package/dist/src/priorresolve.js +275 -0
  525. package/dist/src/priorresolve.js.map +1 -0
  526. package/dist/src/qcrbin.js +180 -0
  527. package/dist/src/qcrbin.js.map +1 -0
  528. package/dist/src/recheck.js +1398 -0
  529. package/dist/src/recheck.js.map +1 -0
  530. package/dist/src/reconcile.js +820 -0
  531. package/dist/src/reconcile.js.map +1 -0
  532. package/dist/src/recordedfindings.js +15 -0
  533. package/dist/src/recordedfindings.js.map +1 -0
  534. package/dist/src/regression.js +546 -0
  535. package/dist/src/regression.js.map +1 -0
  536. package/dist/src/replay.js +598 -0
  537. package/dist/src/replay.js.map +1 -0
  538. package/dist/src/requiredpasses.js +113 -0
  539. package/dist/src/requiredpasses.js.map +1 -0
  540. package/dist/src/resumecheck.js +168 -0
  541. package/dist/src/resumecheck.js.map +1 -0
  542. package/dist/src/reviewcli.js +671 -0
  543. package/dist/src/reviewcli.js.map +1 -0
  544. package/dist/src/reviewcontext.js +22 -0
  545. package/dist/src/reviewcontext.js.map +1 -0
  546. package/dist/src/reviewdetach.js +574 -0
  547. package/dist/src/reviewdetach.js.map +1 -0
  548. package/dist/src/reviewhistory.js +284 -0
  549. package/dist/src/reviewhistory.js.map +1 -0
  550. package/dist/src/reviewhome.js +26 -0
  551. package/dist/src/reviewhome.js.map +1 -0
  552. package/dist/src/reviewmemory.js +110 -0
  553. package/dist/src/reviewmemory.js.map +1 -0
  554. package/dist/src/reviewrendercli.js +104 -0
  555. package/dist/src/reviewrendercli.js.map +1 -0
  556. package/dist/src/round-work.js +20 -0
  557. package/dist/src/round-work.js.map +1 -0
  558. package/dist/src/runhost.js +72 -0
  559. package/dist/src/runhost.js.map +1 -0
  560. package/dist/src/scanauth.js +25 -0
  561. package/dist/src/scanauth.js.map +1 -0
  562. package/dist/src/scanbox.js +846 -0
  563. package/dist/src/scanbox.js.map +1 -0
  564. package/dist/src/scancli.js +1074 -0
  565. package/dist/src/scancli.js.map +1 -0
  566. package/dist/src/scandriverlog.js +30 -0
  567. package/dist/src/scandriverlog.js.map +1 -0
  568. package/dist/src/session-bridge.js +75 -0
  569. package/dist/src/session-bridge.js.map +1 -0
  570. package/dist/src/session-command-payload.js +29 -0
  571. package/dist/src/session-command-payload.js.map +1 -0
  572. package/dist/src/session-command-supervisor.js +939 -0
  573. package/dist/src/session-command-supervisor.js.map +1 -0
  574. package/dist/src/sessionjsonl.js +85 -0
  575. package/dist/src/sessionjsonl.js.map +1 -0
  576. package/dist/src/sessions.js +541 -0
  577. package/dist/src/sessions.js.map +1 -0
  578. package/dist/src/setup/adapters.js +330 -0
  579. package/dist/src/setup/adapters.js.map +1 -0
  580. package/dist/src/setup/baselines.js +64 -0
  581. package/dist/src/setup/baselines.js.map +1 -0
  582. package/dist/src/setup/caserunner.js +554 -0
  583. package/dist/src/setup/caserunner.js.map +1 -0
  584. package/dist/src/setup/configcommit.js +358 -0
  585. package/dist/src/setup/configcommit.js.map +1 -0
  586. package/dist/src/setup/engine.js +672 -0
  587. package/dist/src/setup/engine.js.map +1 -0
  588. package/dist/src/setup/facts.js +593 -0
  589. package/dist/src/setup/facts.js.map +1 -0
  590. package/dist/src/setup/findings.js +948 -0
  591. package/dist/src/setup/findings.js.map +1 -0
  592. package/dist/src/setup/history.js +338 -0
  593. package/dist/src/setup/history.js.map +1 -0
  594. package/dist/src/setup/interview.js +185 -0
  595. package/dist/src/setup/interview.js.map +1 -0
  596. package/dist/src/setup/ledger.js +207 -0
  597. package/dist/src/setup/ledger.js.map +1 -0
  598. package/dist/src/setup/pins.js +43 -0
  599. package/dist/src/setup/pins.js.map +1 -0
  600. package/dist/src/setup/portrait.js +432 -0
  601. package/dist/src/setup/portrait.js.map +1 -0
  602. package/dist/src/setup/presentation.js +9 -0
  603. package/dist/src/setup/presentation.js.map +1 -0
  604. package/dist/src/setup/profile.js +417 -0
  605. package/dist/src/setup/profile.js.map +1 -0
  606. package/dist/src/setup/replay.js +240 -0
  607. package/dist/src/setup/replay.js.map +1 -0
  608. package/dist/src/setup/replaybuild.js +311 -0
  609. package/dist/src/setup/replaybuild.js.map +1 -0
  610. package/dist/src/setup/replayrun.js +806 -0
  611. package/dist/src/setup/replayrun.js.map +1 -0
  612. package/dist/src/setup/replaysource.js +169 -0
  613. package/dist/src/setup/replaysource.js.map +1 -0
  614. package/dist/src/setup/retune.js +333 -0
  615. package/dist/src/setup/retune.js.map +1 -0
  616. package/dist/src/setup/retunelog.js +51 -0
  617. package/dist/src/setup/retunelog.js.map +1 -0
  618. package/dist/src/setup/scan.js +725 -0
  619. package/dist/src/setup/scan.js.map +1 -0
  620. package/dist/src/setup/scanbuild.js +133 -0
  621. package/dist/src/setup/scanbuild.js.map +1 -0
  622. package/dist/src/setup/scanfacts.js +96 -0
  623. package/dist/src/setup/scanfacts.js.map +1 -0
  624. package/dist/src/setup/scanlease.js +251 -0
  625. package/dist/src/setup/scanlease.js.map +1 -0
  626. package/dist/src/setup/scanreport.js +289 -0
  627. package/dist/src/setup/scanreport.js.map +1 -0
  628. package/dist/src/setup/scanrun.js +1733 -0
  629. package/dist/src/setup/scanrun.js.map +1 -0
  630. package/dist/src/setup/scantree.js +257 -0
  631. package/dist/src/setup/scantree.js.map +1 -0
  632. package/dist/src/setup/stage2.js +617 -0
  633. package/dist/src/setup/stage2.js.map +1 -0
  634. package/dist/src/setup/stage2request.js +259 -0
  635. package/dist/src/setup/stage2request.js.map +1 -0
  636. package/dist/src/setup/survey.js +307 -0
  637. package/dist/src/setup/survey.js.map +1 -0
  638. package/dist/src/setup/surveyrun.js +549 -0
  639. package/dist/src/setup/surveyrun.js.map +1 -0
  640. package/dist/src/setup/textsniff.js +89 -0
  641. package/dist/src/setup/textsniff.js.map +1 -0
  642. package/dist/src/setup/trackersink.js +395 -0
  643. package/dist/src/setup/trackersink.js.map +1 -0
  644. package/dist/src/setup/triage.js +636 -0
  645. package/dist/src/setup/triage.js.map +1 -0
  646. package/dist/src/setup/tuning.js +585 -0
  647. package/dist/src/setup/tuning.js.map +1 -0
  648. package/dist/src/setup/unitcut.js +437 -0
  649. package/dist/src/setup/unitcut.js.map +1 -0
  650. package/dist/src/singleflight.js +72 -0
  651. package/dist/src/singleflight.js.map +1 -0
  652. package/dist/src/slack/announce.js +725 -0
  653. package/dist/src/slack/announce.js.map +1 -0
  654. package/dist/src/slack/api.js +227 -0
  655. package/dist/src/slack/api.js.map +1 -0
  656. package/dist/src/slack/messages.js +158 -0
  657. package/dist/src/slack/messages.js.map +1 -0
  658. package/dist/src/slack/origin.js +550 -0
  659. package/dist/src/slack/origin.js.map +1 -0
  660. package/dist/src/slack/policy.js +225 -0
  661. package/dist/src/slack/policy.js.map +1 -0
  662. package/dist/src/slack/reply.js +153 -0
  663. package/dist/src/slack/reply.js.map +1 -0
  664. package/dist/src/slack/router.js +189 -0
  665. package/dist/src/slack/router.js.map +1 -0
  666. package/dist/src/slack/socket.js +323 -0
  667. package/dist/src/slack/socket.js.map +1 -0
  668. package/dist/src/slack/types.js +11 -0
  669. package/dist/src/slack/types.js.map +1 -0
  670. package/dist/src/sqlitehint.js +30 -0
  671. package/dist/src/sqlitehint.js.map +1 -0
  672. package/dist/src/ssh-observation.js +25 -0
  673. package/dist/src/ssh-observation.js.map +1 -0
  674. package/dist/src/state.js +5869 -0
  675. package/dist/src/state.js.map +1 -0
  676. package/dist/src/statemachine.js +220 -0
  677. package/dist/src/statemachine.js.map +1 -0
  678. package/dist/src/stubs.js +52 -0
  679. package/dist/src/stubs.js.map +1 -0
  680. package/dist/src/takeover.js +506 -0
  681. package/dist/src/takeover.js.map +1 -0
  682. package/dist/src/takeoverpi.js +249 -0
  683. package/dist/src/takeoverpi.js.map +1 -0
  684. package/dist/src/terminal-failure.js +26 -0
  685. package/dist/src/terminal-failure.js.map +1 -0
  686. package/dist/src/thread-resolution-refusal.js +4 -0
  687. package/dist/src/thread-resolution-refusal.js.map +1 -0
  688. package/dist/src/tmux.js +126 -0
  689. package/dist/src/tmux.js.map +1 -0
  690. package/dist/src/trackerid.js +43 -0
  691. package/dist/src/trackerid.js.map +1 -0
  692. package/dist/src/transcriptindex.js +512 -0
  693. package/dist/src/transcriptindex.js.map +1 -0
  694. package/dist/src/tui/actions.js +404 -0
  695. package/dist/src/tui/actions.js.map +1 -0
  696. package/dist/src/tui/alerts.js +523 -0
  697. package/dist/src/tui/alerts.js.map +1 -0
  698. package/dist/src/tui/app.js +1835 -0
  699. package/dist/src/tui/app.js.map +1 -0
  700. package/dist/src/tui/archiveops.js +164 -0
  701. package/dist/src/tui/archiveops.js.map +1 -0
  702. package/dist/src/tui/autofile.js +191 -0
  703. package/dist/src/tui/autofile.js.map +1 -0
  704. package/dist/src/tui/board.js +1143 -0
  705. package/dist/src/tui/board.js.map +1 -0
  706. package/dist/src/tui/candidates.js +699 -0
  707. package/dist/src/tui/candidates.js.map +1 -0
  708. package/dist/src/tui/cellwidth.js +151 -0
  709. package/dist/src/tui/cellwidth.js.map +1 -0
  710. package/dist/src/tui/changelog-store.js +404 -0
  711. package/dist/src/tui/changelog-store.js.map +1 -0
  712. package/dist/src/tui/chord.js +26 -0
  713. package/dist/src/tui/chord.js.map +1 -0
  714. package/dist/src/tui/clipboard.js +81 -0
  715. package/dist/src/tui/clipboard.js.map +1 -0
  716. package/dist/src/tui/cmdbar.js +253 -0
  717. package/dist/src/tui/cmdbar.js.map +1 -0
  718. package/dist/src/tui/companion.js +1275 -0
  719. package/dist/src/tui/companion.js.map +1 -0
  720. package/dist/src/tui/companionplan.js +200 -0
  721. package/dist/src/tui/companionplan.js.map +1 -0
  722. package/dist/src/tui/companiontab.js +61 -0
  723. package/dist/src/tui/companiontab.js.map +1 -0
  724. package/dist/src/tui/confighome.js +105 -0
  725. package/dist/src/tui/confighome.js.map +1 -0
  726. package/dist/src/tui/connect.js +44 -0
  727. package/dist/src/tui/connect.js.map +1 -0
  728. package/dist/src/tui/deploy-ceremony.js +75 -0
  729. package/dist/src/tui/deploy-ceremony.js.map +1 -0
  730. package/dist/src/tui/deploy-drain.js +93 -0
  731. package/dist/src/tui/deploy-drain.js.map +1 -0
  732. package/dist/src/tui/deploy-process.js +234 -0
  733. package/dist/src/tui/deploy-process.js.map +1 -0
  734. package/dist/src/tui/deploytab.js +1263 -0
  735. package/dist/src/tui/deploytab.js.map +1 -0
  736. package/dist/src/tui/design.js +111 -0
  737. package/dist/src/tui/design.js.map +1 -0
  738. package/dist/src/tui/digesttab.js +148 -0
  739. package/dist/src/tui/digesttab.js.map +1 -0
  740. package/dist/src/tui/emptystate.js +32 -0
  741. package/dist/src/tui/emptystate.js.map +1 -0
  742. package/dist/src/tui/evidence.js +790 -0
  743. package/dist/src/tui/evidence.js.map +1 -0
  744. package/dist/src/tui/feed.js +415 -0
  745. package/dist/src/tui/feed.js.map +1 -0
  746. package/dist/src/tui/feedbacktab.js +907 -0
  747. package/dist/src/tui/feedbacktab.js.map +1 -0
  748. package/dist/src/tui/filters.js +72 -0
  749. package/dist/src/tui/filters.js.map +1 -0
  750. package/dist/src/tui/ghread.js +272 -0
  751. package/dist/src/tui/ghread.js.map +1 -0
  752. package/dist/src/tui/historypull.js +61 -0
  753. package/dist/src/tui/historypull.js.map +1 -0
  754. package/dist/src/tui/inboxpull.js +197 -0
  755. package/dist/src/tui/inboxpull.js.map +1 -0
  756. package/dist/src/tui/index.js +1425 -0
  757. package/dist/src/tui/index.js.map +1 -0
  758. package/dist/src/tui/issuesink.js +206 -0
  759. package/dist/src/tui/issuesink.js.map +1 -0
  760. package/dist/src/tui/jsonfile.js +32 -0
  761. package/dist/src/tui/jsonfile.js.map +1 -0
  762. package/dist/src/tui/keys.js +97 -0
  763. package/dist/src/tui/keys.js.map +1 -0
  764. package/dist/src/tui/layout.js +70 -0
  765. package/dist/src/tui/layout.js.map +1 -0
  766. package/dist/src/tui/learning.js +469 -0
  767. package/dist/src/tui/learning.js.map +1 -0
  768. package/dist/src/tui/lessonswitch.js +103 -0
  769. package/dist/src/tui/lessonswitch.js.map +1 -0
  770. package/dist/src/tui/livecontrol.js +167 -0
  771. package/dist/src/tui/livecontrol.js.map +1 -0
  772. package/dist/src/tui/memforward.js +310 -0
  773. package/dist/src/tui/memforward.js.map +1 -0
  774. package/dist/src/tui/memincident.js +287 -0
  775. package/dist/src/tui/memincident.js.map +1 -0
  776. package/dist/src/tui/memory.js +485 -0
  777. package/dist/src/tui/memory.js.map +1 -0
  778. package/dist/src/tui/memtab.js +513 -0
  779. package/dist/src/tui/memtab.js.map +1 -0
  780. package/dist/src/tui/metricstab.js +979 -0
  781. package/dist/src/tui/metricstab.js.map +1 -0
  782. package/dist/src/tui/model.js +104 -0
  783. package/dist/src/tui/model.js.map +1 -0
  784. package/dist/src/tui/mouse.js +83 -0
  785. package/dist/src/tui/mouse.js.map +1 -0
  786. package/dist/src/tui/msg.js +196 -0
  787. package/dist/src/tui/msg.js.map +1 -0
  788. package/dist/src/tui/nativecompanion.js +321 -0
  789. package/dist/src/tui/nativecompanion.js.map +1 -0
  790. package/dist/src/tui/openurl.js +17 -0
  791. package/dist/src/tui/openurl.js.map +1 -0
  792. package/dist/src/tui/paint.js +501 -0
  793. package/dist/src/tui/paint.js.map +1 -0
  794. package/dist/src/tui/panes.js +74 -0
  795. package/dist/src/tui/panes.js.map +1 -0
  796. package/dist/src/tui/pipane.js +166 -0
  797. package/dist/src/tui/pipane.js.map +1 -0
  798. package/dist/src/tui/poll-hooks.js +11 -0
  799. package/dist/src/tui/poll-hooks.js.map +1 -0
  800. package/dist/src/tui/poll.js +113 -0
  801. package/dist/src/tui/poll.js.map +1 -0
  802. package/dist/src/tui/proposals.js +715 -0
  803. package/dist/src/tui/proposals.js.map +1 -0
  804. package/dist/src/tui/prpause.js +132 -0
  805. package/dist/src/tui/prpause.js.map +1 -0
  806. package/dist/src/tui/reload.js +351 -0
  807. package/dist/src/tui/reload.js.map +1 -0
  808. package/dist/src/tui/remote.js +312 -0
  809. package/dist/src/tui/remote.js.map +1 -0
  810. package/dist/src/tui/rollover.js +767 -0
  811. package/dist/src/tui/rollover.js.map +1 -0
  812. package/dist/src/tui/scantab.js +1053 -0
  813. package/dist/src/tui/scantab.js.map +1 -0
  814. package/dist/src/tui/setupentry.js +81 -0
  815. package/dist/src/tui/setupentry.js.map +1 -0
  816. package/dist/src/tui/sidebar.js +602 -0
  817. package/dist/src/tui/sidebar.js.map +1 -0
  818. package/dist/src/tui/sshtriage.js +393 -0
  819. package/dist/src/tui/sshtriage.js.map +1 -0
  820. package/dist/src/tui/startup.js +172 -0
  821. package/dist/src/tui/startup.js.map +1 -0
  822. package/dist/src/tui/tabs.js +41 -0
  823. package/dist/src/tui/tabs.js.map +1 -0
  824. package/dist/src/tui/termmirror.js +217 -0
  825. package/dist/src/tui/termmirror.js.map +1 -0
  826. package/dist/src/tui/theme.js +95 -0
  827. package/dist/src/tui/theme.js.map +1 -0
  828. package/dist/src/tui/ticketstab.js +224 -0
  829. package/dist/src/tui/ticketstab.js.map +1 -0
  830. package/dist/src/tui/tmuxsocket.js +101 -0
  831. package/dist/src/tui/tmuxsocket.js.map +1 -0
  832. package/dist/src/tui/trackertickets.js +77 -0
  833. package/dist/src/tui/trackertickets.js.map +1 -0
  834. package/dist/src/tui/unwedge.js +245 -0
  835. package/dist/src/tui/unwedge.js.map +1 -0
  836. package/dist/src/tui/viewer.js +479 -0
  837. package/dist/src/tui/viewer.js.map +1 -0
  838. package/dist/src/tui/viewerinput.js +125 -0
  839. package/dist/src/tui/viewerinput.js.map +1 -0
  840. package/dist/src/updatenotice.js +143 -0
  841. package/dist/src/updatenotice.js.map +1 -0
  842. package/dist/src/usage.js +1007 -0
  843. package/dist/src/usage.js.map +1 -0
  844. package/dist/src/verdicts.js +3848 -0
  845. package/dist/src/verdicts.js.map +1 -0
  846. package/dist/src/version.js +58 -0
  847. package/dist/src/version.js.map +1 -0
  848. package/dist/src/wakes.js +411 -0
  849. package/dist/src/wakes.js.map +1 -0
  850. package/dist/src/webhook.js +725 -0
  851. package/dist/src/webhook.js.map +1 -0
  852. package/dist/src/webhookheal.js +238 -0
  853. package/dist/src/webhookheal.js.map +1 -0
  854. package/dist/src/wire.js +1801 -0
  855. package/dist/src/wire.js.map +1 -0
  856. package/dist/src/worker-session-host.js +531 -0
  857. package/dist/src/worker-session-host.js.map +1 -0
  858. package/dist/src/workerbreaker.js +112 -0
  859. package/dist/src/workerbreaker.js.map +1 -0
  860. package/dist/src/workercontrol.js +49 -0
  861. package/dist/src/workercontrol.js.map +1 -0
  862. package/dist/src/workerlease.js +351 -0
  863. package/dist/src/workerlease.js.map +1 -0
  864. package/dist/src/workerliveness.js +84 -0
  865. package/dist/src/workerliveness.js.map +1 -0
  866. package/dist/src/workerlog.js +65 -0
  867. package/dist/src/workerlog.js.map +1 -0
  868. package/dist/src/workerruntime.js +10 -0
  869. package/dist/src/workerruntime.js.map +1 -0
  870. package/dist/src/workersupervisor.js +1206 -0
  871. package/dist/src/workersupervisor.js.map +1 -0
  872. package/dist/src/workerusage.js +23 -0
  873. package/dist/src/workerusage.js.map +1 -0
  874. package/dist/src/workerworld.js +332 -0
  875. package/dist/src/workerworld.js.map +1 -0
  876. package/dist/src/worktrees.js +984 -0
  877. package/dist/src/worktrees.js.map +1 -0
  878. package/docker/Dockerfile +284 -0
  879. package/docker/Dockerfile.dockerignore +35 -0
  880. package/docker/README.md +221 -0
  881. package/docker/companion/Dockerfile +139 -0
  882. package/docker/companion/agents/evidence.md +34 -0
  883. package/docker/companion/agents/explore.md +26 -0
  884. package/docker/companion/agents/pass-survey.md +49 -0
  885. package/docker/companion/agents/study.md +26 -0
  886. package/docker/companion/entrypoint.sh +343 -0
  887. package/docker/companion/memory-health.mjs +269 -0
  888. package/docker/entrypoint.sh +782 -0
  889. package/docker/memory/Dockerfile +191 -0
  890. package/docker/memory/Dockerfile.dockerignore +20 -0
  891. package/docker/memory/README.md +60 -0
  892. package/docker/memory/entrypoint.sh +163 -0
  893. package/docker/setid/qcr-setid.c +109 -0
  894. package/docker/tunnel/Dockerfile.cloudflared +51 -0
  895. package/docker/tunnel/Dockerfile.ngrok +40 -0
  896. package/docker/tunnel/sidecar.mjs +411 -0
  897. package/docker/vm/tmux.conf +26 -0
  898. package/docker-compose.yml +348 -0
  899. package/extensions/cli/bash-guard-ambient.ts +71 -0
  900. package/extensions/cli/bash-guard.ts +12 -0
  901. package/extensions/cli/companion-guard.ts +8 -0
  902. package/extensions/cli/debug-round.ts +9 -0
  903. package/extensions/cli/inject-bridge.ts +8 -0
  904. package/extensions/cli/ledger-pin.ts +13 -0
  905. package/extensions/cli/lens.ts +22 -0
  906. package/extensions/cli/muted-theme.ts +9 -0
  907. package/extensions/cli/onboarding.ts +8 -0
  908. package/extensions/cli/setup.ts +8 -0
  909. package/extensions/cli/tui-view.ts +8 -0
  910. package/extensions/companion-guard.ts +411 -0
  911. package/extensions/debug-round.ts +281 -0
  912. package/extensions/inject-bridge.ts +630 -0
  913. package/extensions/inline-bash.ts +102 -0
  914. package/extensions/muted-theme.json +68 -0
  915. package/extensions/muted-theme.ts +23 -0
  916. package/extensions/onboarding.ts +233 -0
  917. package/extensions/setup-replay.ts +336 -0
  918. package/extensions/setup-scan.ts +1236 -0
  919. package/extensions/setup-triage.ts +190 -0
  920. package/extensions/setup.ts +1428 -0
  921. package/extensions/tui-view.ts +489 -0
  922. package/extensions/vendor/openviking-pi/LICENSE +201 -0
  923. package/extensions/vendor/openviking-pi/NOTICE +68 -0
  924. package/extensions/vendor/openviking-pi/README.md +550 -0
  925. package/extensions/vendor/openviking-pi/client.ts +588 -0
  926. package/extensions/vendor/openviking-pi/config.json +28 -0
  927. package/extensions/vendor/openviking-pi/config.ts +271 -0
  928. package/extensions/vendor/openviking-pi/index.ts +523 -0
  929. package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
  930. package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
  931. package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
  932. package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
  933. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
  934. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
  935. package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
  936. package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
  937. package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
  938. package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
  939. package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
  940. package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
  941. package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
  942. package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
  943. package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
  944. package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
  945. package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
  946. package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
  947. package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
  948. package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
  949. package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
  950. package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
  951. package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
  952. package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
  953. package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
  954. package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
  955. package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
  956. package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
  957. package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
  958. package/extensions/vendor/openviking-pi/recall.ts +149 -0
  959. package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
  960. package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
  961. package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
  962. package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
  963. package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
  964. package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
  965. package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
  966. package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
  967. package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
  968. package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
  969. package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
  970. package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
  971. package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
  972. package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
  973. package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
  974. package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
  975. package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  976. package/extensions/vendor/openviking-pi/sync.ts +163 -0
  977. package/extensions/vendor/openviking-pi/takeover.ts +36 -0
  978. package/extensions/vendor/openviking-pi/tools.ts +479 -0
  979. package/extensions/vendor/openviking-pi/upstream.json +48 -0
  980. package/package.json +138 -4
  981. package/profiles/default/README.md +21 -0
  982. package/profiles/default/addenda/.gitkeep +0 -0
  983. package/profiles/default/profile.toml +17 -0
  984. package/profiles/example-django/README.md +21 -0
  985. package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
  986. package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
  987. package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
  988. package/profiles/example-django/addenda/40-orm-usage.md +19 -0
  989. package/profiles/example-django/addenda/50-declared-shape.md +10 -0
  990. package/profiles/example-django/addenda/60-config-placement.md +14 -0
  991. package/profiles/example-django/heuristics.toml +24 -0
  992. package/profiles/example-django/profile.toml +9 -0
  993. package/profiles/fast/README.md +24 -0
  994. package/profiles/fast/profile.toml +59 -0
  995. package/profiles/minimal/README.md +17 -0
  996. package/profiles/minimal/addenda/.gitkeep +0 -0
  997. package/profiles/minimal/profile.toml +8 -0
  998. package/profiles/snappy/README.md +24 -0
  999. package/profiles/snappy/addenda/10-race-as-question.md +6 -0
  1000. package/profiles/snappy/profile.toml +46 -0
  1001. package/prompts/companion-day-open.md +46 -0
  1002. package/prompts/companion-first-run.md +104 -0
  1003. package/prompts/companion.md +821 -0
  1004. package/prompts/conversation-system.md +117 -0
  1005. package/prompts/eod-sweep.md +120 -0
  1006. package/prompts/onboarding.md +74 -0
  1007. package/prompts/pass-common.md +52 -0
  1008. package/prompts/pass-quality-common.md +22 -0
  1009. package/prompts/round-first.md +874 -0
  1010. package/prompts/round-follow-up.md +991 -0
  1011. package/prompts/round-re-review.md +889 -0
  1012. package/prompts/round-recheck.md +196 -0
  1013. package/prompts/round-staleness.md +856 -0
  1014. package/schemas/review-result.v1.json +391 -0
  1015. package/schemas/scan-result.v1.json +421 -0
  1016. package/schemas/version.v1.json +57 -0
  1017. package/scripts/approve-pr.sh +16 -0
  1018. package/scripts/build-stamp.mjs +67 -0
  1019. package/scripts/commit-verdict.sh +155 -0
  1020. package/scripts/companion-e2e.sh +250 -0
  1021. package/scripts/companion-memory-forward.mjs +1966 -0
  1022. package/scripts/config-fingerprint.sh +28 -0
  1023. package/scripts/deploy-manifest.mjs +141 -0
  1024. package/scripts/deploy.sh +1435 -0
  1025. package/scripts/directory-lock.mjs +193 -0
  1026. package/scripts/heartbeat-fresh.mjs +54 -0
  1027. package/scripts/install-locator.sh +328 -0
  1028. package/scripts/install-wrapper.sh +76 -0
  1029. package/scripts/memory-search-check.mjs +98 -0
  1030. package/scripts/mint-token.sh +212 -0
  1031. package/scripts/openviking-embedder.py +392 -0
  1032. package/scripts/openviking-ov-conf.mjs +161 -0
  1033. package/scripts/openviking-provision.mjs +1122 -0
  1034. package/scripts/openviking-reembed.mjs +1154 -0
  1035. package/scripts/post-review.sh +16 -0
  1036. package/scripts/pr-checks.sh +256 -0
  1037. package/scripts/pr-context.py +174 -0
  1038. package/scripts/pr-context.sh +63 -0
  1039. package/scripts/pr-self.sh +144 -0
  1040. package/scripts/pr-threads.sh +145 -0
  1041. package/scripts/qcr +15 -0
  1042. package/scripts/qcr-host.sh +769 -0
  1043. package/scripts/render-review.sh +24 -0
  1044. package/scripts/reply-thread.sh +16 -0
  1045. package/scripts/required-passes.py +112 -0
  1046. package/scripts/smoke-companion.sh +211 -0
  1047. package/scripts/tui-deploy-runner.mjs +61 -0
  1048. package/scripts/verify-dist.mjs +201 -0
  1049. package/scripts/vm-ghcr-login.sh +188 -0
  1050. package/seeds/agents/pass-baseline.md +61 -0
  1051. package/seeds/agents/pass-contracts.md +30 -0
  1052. package/seeds/agents/pass-data-safety.md +54 -0
  1053. package/seeds/agents/pass-delivery.md +37 -0
  1054. package/seeds/agents/pass-prod-risk.md +24 -0
  1055. package/seeds/agents/pass-security.md +31 -0
  1056. package/seeds/agents/pass-statelessness.md +27 -0
  1057. package/seeds/agents/pass-ui.md +23 -0
  1058. package/seeds/agents/pass-validate.md +50 -0
  1059. package/seeds/prompts/conversation-system.md +117 -0
  1060. package/seeds/prompts/eod-sweep.md +120 -0
  1061. package/seeds/prompts/pass-common.md +52 -0
  1062. package/skills/companion/adjudicate/SKILL.md +247 -0
  1063. package/skills/companion/evidence/SKILL.md +150 -0
  1064. package/skills/companion/land/SKILL.md +200 -0
  1065. package/skills/companion/replay/SKILL.md +138 -0
  1066. package/skills/companion/setup/SKILL.md +167 -0
  1067. package/skills/companion/survey/SKILL.md +160 -0
  1068. package/skills/selfimprove/README.md +34 -0
  1069. package/skills/selfimprove/SKILL.md +127 -0
  1070. package/skills/selfimprove/agents/openai.yaml +2 -0
  1071. package/skills/selfimprove/commands/selfimprove.md +6 -0
  1072. package/skills/selfimprove/references/fix-loop.md +49 -0
  1073. package/skills/selfimprove/references/ledger.md +20 -0
  1074. package/skills/selfimprove/references/setup.md +40 -0
  1075. package/src/anomalies.ts +566 -0
  1076. package/src/answerworker.ts +251 -0
  1077. package/src/appstamp.ts +65 -0
  1078. package/src/archive/admission.ts +3 -0
  1079. package/src/archive/archivedb.ts +548 -0
  1080. package/src/archive/archiver.ts +1963 -0
  1081. package/src/archive/archivewrite.ts +279 -0
  1082. package/src/archive/bundle.ts +333 -0
  1083. package/src/archive/dbmove.ts +911 -0
  1084. package/src/archive/duplicatecounts.ts +98 -0
  1085. package/src/archive/eligibility.ts +181 -0
  1086. package/src/archive/extract.ts +342 -0
  1087. package/src/archive/fsutil.ts +415 -0
  1088. package/src/archive/gzfile.ts +370 -0
  1089. package/src/archive/kinds.ts +21 -0
  1090. package/src/archive/livecheckpoint.ts +217 -0
  1091. package/src/archive/liveretention.ts +180 -0
  1092. package/src/archive/manifest.ts +519 -0
  1093. package/src/archive/overseer.ts +336 -0
  1094. package/src/archive/piglobal.ts +174 -0
  1095. package/src/archive/plan.ts +612 -0
  1096. package/src/archive/prarchive.ts +984 -0
  1097. package/src/archive/prfiles.ts +406 -0
  1098. package/src/archive/report.ts +252 -0
  1099. package/src/archive/restore.ts +330 -0
  1100. package/src/archive/rows.ts +187 -0
  1101. package/src/archive/seal.ts +630 -0
  1102. package/src/archive/status.ts +719 -0
  1103. package/src/archive/storepass.ts +581 -0
  1104. package/src/backfill.ts +362 -0
  1105. package/src/bench/cost.ts +113 -0
  1106. package/src/bench/exec.ts +90 -0
  1107. package/src/bench/values.ts +13 -0
  1108. package/src/botcontrol.ts +176 -0
  1109. package/src/botworker.ts +953 -0
  1110. package/src/capture.ts +321 -0
  1111. package/src/cases.ts +1789 -0
  1112. package/src/channelfs.ts +26 -0
  1113. package/src/checklist.ts +506 -0
  1114. package/src/ciretry.ts +141 -0
  1115. package/src/cli.ts +2271 -0
  1116. package/src/cliops.ts +384 -0
  1117. package/src/clonedir.ts +15 -0
  1118. package/src/commandeffects.ts +57 -0
  1119. package/src/commands.ts +1678 -0
  1120. package/src/companioninbox.ts +239 -0
  1121. package/src/compare.ts +957 -0
  1122. package/src/config.ts +2050 -0
  1123. package/src/configcheck.ts +133 -0
  1124. package/src/coreext.ts +47 -0
  1125. package/src/daemon.ts +1302 -0
  1126. package/src/debuground.ts +927 -0
  1127. package/src/debugroundpi.ts +262 -0
  1128. package/src/diagnostics.ts +14 -0
  1129. package/src/digest.ts +183 -0
  1130. package/src/dispatch.ts +2250 -0
  1131. package/src/engine/answerguard.ts +27 -0
  1132. package/src/engine/botsdk.ts +336 -0
  1133. package/src/engine/caserunner-sdk.ts +423 -0
  1134. package/src/engine/debugpi.ts +235 -0
  1135. package/src/eod.ts +669 -0
  1136. package/src/eodpinwatch.ts +556 -0
  1137. package/src/escalation.ts +1253 -0
  1138. package/src/eventloop.ts +1045 -0
  1139. package/src/execboundary.ts +633 -0
  1140. package/src/feedback.ts +1162 -0
  1141. package/src/findingscheck.ts +298 -0
  1142. package/src/fsdenied.ts +24 -0
  1143. package/src/ghread.ts +242 -0
  1144. package/src/github.ts +1497 -0
  1145. package/src/homes.ts +175 -0
  1146. package/src/hostroute.ts +53 -0
  1147. package/src/improvements.ts +787 -0
  1148. package/src/install.ts +1103 -0
  1149. package/src/installkind.ts +50 -0
  1150. package/src/ipc/capability.ts +333 -0
  1151. package/src/ipc/client.ts +269 -0
  1152. package/src/ipc/control.ts +90 -0
  1153. package/src/ipc/ghreadshim.ts +94 -0
  1154. package/src/ipc/launcher.ts +65 -0
  1155. package/src/ipc/permits.ts +362 -0
  1156. package/src/ipc/postshim.ts +164 -0
  1157. package/src/ipc/protocol.ts +342 -0
  1158. package/src/ipc/server.ts +371 -0
  1159. package/src/ipc/workerspec.ts +218 -0
  1160. package/src/learner/learner.ts +1655 -0
  1161. package/src/learner/miner.ts +1290 -0
  1162. package/src/learner/packet.ts +340 -0
  1163. package/src/learner/receipt.ts +168 -0
  1164. package/src/learner/status.ts +107 -0
  1165. package/src/ledgercheck.ts +157 -0
  1166. package/src/lessonledger.ts +703 -0
  1167. package/src/lessonselect.ts +888 -0
  1168. package/src/lessonshadow.ts +810 -0
  1169. package/src/levers.ts +393 -0
  1170. package/src/live/projection.ts +491 -0
  1171. package/src/live/publish.ts +92 -0
  1172. package/src/live/read.ts +86 -0
  1173. package/src/livecli.ts +336 -0
  1174. package/src/loginhint.ts +56 -0
  1175. package/src/loginrefresh/atomic.ts +76 -0
  1176. package/src/loginrefresh/lock.ts +104 -0
  1177. package/src/loginrefresh/main.ts +59 -0
  1178. package/src/loginrefresh/pirefresh.ts +57 -0
  1179. package/src/loginrefresh/refresher.ts +326 -0
  1180. package/src/loginrefresh/supervisor.ts +273 -0
  1181. package/src/loginrefresh/types.ts +35 -0
  1182. package/src/memory/context.ts +385 -0
  1183. package/src/memory/datafence.ts +9 -0
  1184. package/src/memory/delivery.ts +171 -0
  1185. package/src/memory/eventtags.ts +80 -0
  1186. package/src/memory/health.ts +953 -0
  1187. package/src/memory/import.ts +664 -0
  1188. package/src/memory/layout.ts +261 -0
  1189. package/src/memory/legacy-file.ts +104 -0
  1190. package/src/memory/lessons.ts +447 -0
  1191. package/src/memory/ovsession.ts +601 -0
  1192. package/src/memory/relayout.ts +335 -0
  1193. package/src/memory/roundactivity.ts +209 -0
  1194. package/src/memory/selection.ts +989 -0
  1195. package/src/memory/shadowstatus.ts +125 -0
  1196. package/src/memory/store.ts +275 -0
  1197. package/src/memory/textidentity.ts +8 -0
  1198. package/src/metrics.ts +1056 -0
  1199. package/src/missingconfig.ts +7 -0
  1200. package/src/npmverbs.ts +264 -0
  1201. package/src/onboard/choices.ts +222 -0
  1202. package/src/onboard/engine.ts +518 -0
  1203. package/src/onboard/launch.ts +115 -0
  1204. package/src/onboard/steps/appslug.ts +265 -0
  1205. package/src/onboard/steps/bringup.ts +482 -0
  1206. package/src/onboard/steps/compose.ts +459 -0
  1207. package/src/onboard/steps/configdelivery.ts +58 -0
  1208. package/src/onboard/steps/configfill.ts +1352 -0
  1209. package/src/onboard/steps/deploysh.ts +105 -0
  1210. package/src/onboard/steps/embeddings.ts +550 -0
  1211. package/src/onboard/steps/escalation.ts +640 -0
  1212. package/src/onboard/steps/githubapp.ts +1414 -0
  1213. package/src/onboard/steps/golive.ts +577 -0
  1214. package/src/onboard/steps/index.ts +71 -0
  1215. package/src/onboard/steps/ingress.ts +907 -0
  1216. package/src/onboard/steps/inventory.ts +526 -0
  1217. package/src/onboard/steps/llmauth.ts +1095 -0
  1218. package/src/onboard/steps/memory.ts +1290 -0
  1219. package/src/onboard/steps/memorykeys.ts +85 -0
  1220. package/src/onboard/steps/preflight.ts +191 -0
  1221. package/src/onboard/steps/proof.ts +624 -0
  1222. package/src/onboard/steps/ssh.ts +377 -0
  1223. package/src/onboard/steps/topology.ts +337 -0
  1224. package/src/onboard/steps/transport.ts +665 -0
  1225. package/src/onboard/steps/verifyup.ts +448 -0
  1226. package/src/onboard/steps/vmdeploy.ts +442 -0
  1227. package/src/onboard/toml.ts +50 -0
  1228. package/src/opencli.ts +591 -0
  1229. package/src/openpr.ts +568 -0
  1230. package/src/openviking/client.ts +1131 -0
  1231. package/src/openviking/context.ts +263 -0
  1232. package/src/openviking/embedder.ts +516 -0
  1233. package/src/openviking/gapsink.ts +63 -0
  1234. package/src/openviking/health.ts +83 -0
  1235. package/src/openviking/index.ts +119 -0
  1236. package/src/openviking/lessonfind.ts +92 -0
  1237. package/src/openviking/probe.ts +129 -0
  1238. package/src/openviking/proc.ts +110 -0
  1239. package/src/openviking/provisionreport.ts +160 -0
  1240. package/src/openviking/reembed.ts +412 -0
  1241. package/src/openviking/relayout.ts +177 -0
  1242. package/src/openviking/sidecar-main.ts +38 -0
  1243. package/src/openviking/sidecar.ts +106 -0
  1244. package/src/openviking/store.ts +2025 -0
  1245. package/src/openviking/supervisor.ts +474 -0
  1246. package/src/openviking/types.ts +174 -0
  1247. package/src/operator-recovery.ts +237 -0
  1248. package/src/operatormessage.ts +52 -0
  1249. package/src/outcomes.ts +1670 -0
  1250. package/src/piauthcopy.ts +12 -0
  1251. package/src/picli.ts +49 -0
  1252. package/src/picwd.ts +25 -0
  1253. package/src/pilogin.ts +37 -0
  1254. package/src/pingpong.ts +1339 -0
  1255. package/src/posting-journal.ts +549 -0
  1256. package/src/posting-outbox.ts +802 -0
  1257. package/src/posting-receipt.ts +324 -0
  1258. package/src/posting.ts +603 -0
  1259. package/src/postops.ts +832 -0
  1260. package/src/pr-activity.ts +20 -0
  1261. package/src/pr-author.ts +6 -0
  1262. package/src/pr-conversation.ts +432 -0
  1263. package/src/pr-session-lifecycle.ts +631 -0
  1264. package/src/preflight.ts +391 -0
  1265. package/src/priorresolve.ts +343 -0
  1266. package/src/qcrbin.ts +186 -0
  1267. package/src/recheck.ts +1775 -0
  1268. package/src/reconcile.ts +1030 -0
  1269. package/src/recordedfindings.ts +27 -0
  1270. package/src/regression.ts +833 -0
  1271. package/src/replay.ts +816 -0
  1272. package/src/requiredpasses.ts +148 -0
  1273. package/src/resumecheck.ts +246 -0
  1274. package/src/reviewcli.ts +787 -0
  1275. package/src/reviewcontext.ts +35 -0
  1276. package/src/reviewdetach.ts +734 -0
  1277. package/src/reviewhistory.ts +427 -0
  1278. package/src/reviewhome.ts +36 -0
  1279. package/src/reviewmemory.ts +133 -0
  1280. package/src/reviewrendercli.ts +139 -0
  1281. package/src/round-work.ts +42 -0
  1282. package/src/runhost.ts +108 -0
  1283. package/src/scanauth.ts +36 -0
  1284. package/src/scanbox.ts +1081 -0
  1285. package/src/scancli.ts +1296 -0
  1286. package/src/scandriverlog.ts +32 -0
  1287. package/src/session-bridge.ts +139 -0
  1288. package/src/session-command-payload.ts +35 -0
  1289. package/src/session-command-supervisor.ts +1142 -0
  1290. package/src/sessionjsonl.ts +88 -0
  1291. package/src/sessions.ts +659 -0
  1292. package/src/setup/adapters.ts +335 -0
  1293. package/src/setup/baselines.ts +110 -0
  1294. package/src/setup/caserunner.ts +611 -0
  1295. package/src/setup/configcommit.ts +443 -0
  1296. package/src/setup/engine.ts +1001 -0
  1297. package/src/setup/facts.ts +627 -0
  1298. package/src/setup/findings.ts +1264 -0
  1299. package/src/setup/history.ts +443 -0
  1300. package/src/setup/interview.ts +243 -0
  1301. package/src/setup/ledger.ts +324 -0
  1302. package/src/setup/pins.ts +66 -0
  1303. package/src/setup/portrait.ts +635 -0
  1304. package/src/setup/presentation.ts +18 -0
  1305. package/src/setup/profile.ts +508 -0
  1306. package/src/setup/replay.ts +370 -0
  1307. package/src/setup/replaybuild.ts +392 -0
  1308. package/src/setup/replayrun.ts +1096 -0
  1309. package/src/setup/replaysource.ts +198 -0
  1310. package/src/setup/retune.ts +415 -0
  1311. package/src/setup/retunelog.ts +95 -0
  1312. package/src/setup/scan.ts +1033 -0
  1313. package/src/setup/scanbuild.ts +168 -0
  1314. package/src/setup/scanfacts.ts +107 -0
  1315. package/src/setup/scanlease.ts +297 -0
  1316. package/src/setup/scanreport.ts +373 -0
  1317. package/src/setup/scanrun.ts +2268 -0
  1318. package/src/setup/scantree.ts +259 -0
  1319. package/src/setup/stage2.ts +926 -0
  1320. package/src/setup/stage2request.ts +347 -0
  1321. package/src/setup/survey.ts +458 -0
  1322. package/src/setup/surveyrun.ts +758 -0
  1323. package/src/setup/textsniff.ts +95 -0
  1324. package/src/setup/trackersink.ts +433 -0
  1325. package/src/setup/triage.ts +768 -0
  1326. package/src/setup/tuning.ts +766 -0
  1327. package/src/setup/unitcut.ts +523 -0
  1328. package/src/singleflight.ts +86 -0
  1329. package/src/slack/announce.ts +911 -0
  1330. package/src/slack/api.ts +303 -0
  1331. package/src/slack/messages.ts +288 -0
  1332. package/src/slack/origin.ts +599 -0
  1333. package/src/slack/policy.ts +354 -0
  1334. package/src/slack/reply.ts +201 -0
  1335. package/src/slack/router.ts +285 -0
  1336. package/src/slack/socket.ts +384 -0
  1337. package/src/slack/types.ts +108 -0
  1338. package/src/sqlitehint.ts +32 -0
  1339. package/src/ssh-observation.ts +30 -0
  1340. package/src/state.ts +8243 -0
  1341. package/src/statemachine.ts +369 -0
  1342. package/src/stubs.ts +68 -0
  1343. package/src/takeover.ts +634 -0
  1344. package/src/takeoverpi.ts +293 -0
  1345. package/src/terminal-failure.ts +41 -0
  1346. package/src/thread-resolution-refusal.ts +2 -0
  1347. package/src/tmux.ts +153 -0
  1348. package/src/trackerid.ts +41 -0
  1349. package/src/transcriptindex.ts +665 -0
  1350. package/src/tui/actions.ts +484 -0
  1351. package/src/tui/alerts.ts +666 -0
  1352. package/src/tui/app.ts +1979 -0
  1353. package/src/tui/archiveops.ts +192 -0
  1354. package/src/tui/autofile.ts +233 -0
  1355. package/src/tui/board.ts +1432 -0
  1356. package/src/tui/candidates.ts +889 -0
  1357. package/src/tui/cellwidth.ts +148 -0
  1358. package/src/tui/changelog-store.ts +503 -0
  1359. package/src/tui/chord.ts +25 -0
  1360. package/src/tui/clipboard.ts +92 -0
  1361. package/src/tui/cmdbar.ts +296 -0
  1362. package/src/tui/companion.ts +1427 -0
  1363. package/src/tui/companionplan.ts +247 -0
  1364. package/src/tui/companiontab.ts +83 -0
  1365. package/src/tui/confighome.ts +155 -0
  1366. package/src/tui/connect.ts +68 -0
  1367. package/src/tui/deploy-ceremony.ts +104 -0
  1368. package/src/tui/deploy-drain.ts +129 -0
  1369. package/src/tui/deploy-process.ts +265 -0
  1370. package/src/tui/deploytab.ts +1523 -0
  1371. package/src/tui/design.ts +125 -0
  1372. package/src/tui/digesttab.ts +214 -0
  1373. package/src/tui/emptystate.ts +39 -0
  1374. package/src/tui/evidence.ts +999 -0
  1375. package/src/tui/feed.ts +604 -0
  1376. package/src/tui/feedbacktab.ts +1077 -0
  1377. package/src/tui/filters.ts +90 -0
  1378. package/src/tui/ghread.ts +321 -0
  1379. package/src/tui/historypull.ts +81 -0
  1380. package/src/tui/inboxpull.ts +236 -0
  1381. package/src/tui/index.ts +1565 -0
  1382. package/src/tui/issuesink.ts +238 -0
  1383. package/src/tui/jsonfile.ts +42 -0
  1384. package/src/tui/keys.ts +109 -0
  1385. package/src/tui/layout.ts +108 -0
  1386. package/src/tui/learning.ts +627 -0
  1387. package/src/tui/lessonswitch.ts +118 -0
  1388. package/src/tui/livecontrol.ts +228 -0
  1389. package/src/tui/memforward.ts +443 -0
  1390. package/src/tui/memincident.ts +363 -0
  1391. package/src/tui/memory.ts +620 -0
  1392. package/src/tui/memtab.ts +606 -0
  1393. package/src/tui/metricstab.ts +1177 -0
  1394. package/src/tui/model.ts +131 -0
  1395. package/src/tui/mouse.ts +98 -0
  1396. package/src/tui/msg.ts +268 -0
  1397. package/src/tui/nativecompanion.ts +417 -0
  1398. package/src/tui/openurl.ts +21 -0
  1399. package/src/tui/paint.ts +511 -0
  1400. package/src/tui/panes.ts +98 -0
  1401. package/src/tui/pipane.ts +219 -0
  1402. package/src/tui/poll-hooks.ts +12 -0
  1403. package/src/tui/poll.ts +147 -0
  1404. package/src/tui/proposals.ts +838 -0
  1405. package/src/tui/prpause.ts +150 -0
  1406. package/src/tui/reload.ts +436 -0
  1407. package/src/tui/remote.ts +413 -0
  1408. package/src/tui/rollover.ts +969 -0
  1409. package/src/tui/scantab.ts +1290 -0
  1410. package/src/tui/setupentry.ts +93 -0
  1411. package/src/tui/sidebar.ts +749 -0
  1412. package/src/tui/sshtriage.ts +510 -0
  1413. package/src/tui/startup.ts +231 -0
  1414. package/src/tui/tabs.ts +45 -0
  1415. package/src/tui/termmirror.ts +224 -0
  1416. package/src/tui/theme.ts +95 -0
  1417. package/src/tui/ticketstab.ts +310 -0
  1418. package/src/tui/tmuxsocket.ts +129 -0
  1419. package/src/tui/trackertickets.ts +84 -0
  1420. package/src/tui/unwedge.ts +286 -0
  1421. package/src/tui/viewer.ts +575 -0
  1422. package/src/tui/viewerinput.ts +173 -0
  1423. package/src/updatenotice.ts +178 -0
  1424. package/src/usage.ts +1154 -0
  1425. package/src/verdicts.ts +4689 -0
  1426. package/src/version.ts +80 -0
  1427. package/src/wakes.ts +476 -0
  1428. package/src/webhook.ts +942 -0
  1429. package/src/webhookheal.ts +276 -0
  1430. package/src/wire.ts +2153 -0
  1431. package/src/worker-session-host.ts +602 -0
  1432. package/src/workerbreaker.ts +156 -0
  1433. package/src/workercontrol.ts +104 -0
  1434. package/src/workerlease.ts +441 -0
  1435. package/src/workerliveness.ts +103 -0
  1436. package/src/workerlog.ts +79 -0
  1437. package/src/workerruntime.ts +30 -0
  1438. package/src/workersupervisor.ts +1538 -0
  1439. package/src/workerusage.ts +42 -0
  1440. package/src/workerworld.ts +427 -0
  1441. package/src/worktrees.ts +1138 -0
  1442. package/tests/fixtures/regression-cases.json +795 -0
  1443. package/tests/fixtures/seed-manifest.json +19 -0
@@ -0,0 +1,1432 @@
1
+ // Board screen: semantic truecolor everywhere, rich PR rows (author, last-tick
2
+ // age, rounds, verdict chip), colored glyph legend, section titles with
3
+ // subtitles, clickable zones for full mouse navigation, OSC-8 PR links.
4
+ // Pure: (view, cols, rows) → Frame (+ zones/meta for the input layer).
5
+ import { archiveAttention, readArchiveStatus } from "../archive/status.js";
6
+ import type { WhyPayload } from "../cli.js";
7
+ import { readDaemonLoad, stallShortPhrase } from "../eventloop.js";
8
+ import { curationNeedsAttention, earlierPhrase } from "../learner/status.js";
9
+ import { fmtDuration, isAuthResult, MEMORY_OFF_NO_DOCKER } from "../memory/health.js";
10
+ import { cancelCauseLabel, isLoudCancelCause, type StatusSnapshot } from "../state.js";
11
+ import type { AlertBadge } from "./alerts.js";
12
+ import { barHeight, paintCmdBar, type BarButton, type LiveKeys } from "./cmdbar.js";
13
+ import { filterLabel, visiblePrs, type BoardFilter } from "./filters.js";
14
+ import { glyphFor, type EventLine, type SnapshotPr } from "./model.js";
15
+ import { windowStart } from "./panes.js";
16
+ import {
17
+ ACCENT,
18
+ ACCENT_BG,
19
+ HEADING,
20
+ MUTED,
21
+ RESET as DRESET,
22
+ STRONG,
23
+ WARN,
24
+ modeBadge,
25
+ sectionTitle,
26
+ selected as selectedLine,
27
+ } from "./design.js";
28
+ import {
29
+ Frame,
30
+ charWidth,
31
+ displayWidth,
32
+ fit,
33
+ rule,
34
+ RULE_SEGMENT_SEP,
35
+ sanitizeText,
36
+ stripAnsi,
37
+ truncate,
38
+ wrap,
39
+ } from "./paint.js";
40
+ import type { PiPaneView } from "./pipane.js";
41
+ import { paneSurface } from "./viewer.js";
42
+ import { viewerInputRow, type ViewerInputView } from "./viewerinput.js";
43
+ import type { Health } from "./poll.js";
44
+ import {
45
+ lensForAgent,
46
+ roundExecutionStage,
47
+ type LiveAgentProgress,
48
+ type RoundProgressSnapshot,
49
+ } from "@quickchatai/pi-quickreview/src/round-progress.js";
50
+ import type { ConnectionDiagnosis } from "./sshtriage.js";
51
+ import { FG, RESET, fmtAge, legendLine, osc8, prUrl, stateColor, verdictChip } from "./theme.js";
52
+
53
+ /** Every clickable thing the board declares. */
54
+ export type BoardAction =
55
+ | { t: "select-pr"; pr: number }
56
+ | { t: "open-github"; pr: number }
57
+ | { t: "event-jump"; pr: number }
58
+ | { t: "pi-focus" } // the live pi pane: clicking it types into the session in place
59
+ | { t: "viewer-lens"; lens: string; nth: number } // a progress row: its lens in the viewer
60
+ | { t: "companion" }; // the alert badge: hand the keyboard to the companion column
61
+
62
+ export interface BoardView {
63
+ health: Health;
64
+ /** `undefined` = not yet known. Only a heartbeat sets it: a rendering
65
+ * default here would paint a DRY-RUN badge on a live install. */
66
+ dryRun: boolean | undefined;
67
+ snapshot?: StatusSnapshot;
68
+ /** The triaged reason the daemon cannot be reached, with the exact
69
+ * remedy. Painted where the PR list would be, because that is where the
70
+ * operator is looking when the list is empty. */
71
+ connection?: ConnectionDiagnosis;
72
+ events: EventLine[]; // newest first
73
+ selected: number;
74
+ detail?: WhyPayload;
75
+ /** The EVENTS overlay is open (and owns j/k) — `e` toggles it. */
76
+ eventsFocus: boolean;
77
+ eventsScroll: number;
78
+ help: boolean;
79
+ /** First HELP_LINES entry shown by the overlay (j/k scroll it). */
80
+ helpScroll?: number;
81
+ /** The quit prompt is up. Painted by App over the composed frame,
82
+ * so it covers every tab without any renderer knowing about it. */
83
+ confirmQuit?: boolean;
84
+ /** Live mirror of the selected PR's pi session (board's lower region). */
85
+ pi?: PiPaneView;
86
+ /** Structured parent/lens activity for the selected running round. */
87
+ progress?: RoundProgressSnapshot;
88
+ /** The PR the pane is showing. Defaults to the selection; while the pane is
89
+ * focused it is locked to the session being typed into, so a poll that
90
+ * reorders the list cannot yank the keyboard to another PR. */
91
+ piPr?: number;
92
+ /** The pi pane owns the keyboard: every key but Ctrl-q goes to the session. */
93
+ piFocus?: boolean;
94
+ /** Extra line rendered on the pi pane header (quick-message state). */
95
+ noticeLine?: string;
96
+ /** The viewer's input line (the transcript viewer's pane only). */
97
+ viewerInput?: ViewerInputView;
98
+ /** Replaces the pi pane while the message box is open. */
99
+ inputBox?: { target: string; text: string };
100
+ /** Wall clock for age rendering; injected for testability. */
101
+ now?: Date;
102
+ /** owner/repo for GitHub links (OSC-8 + click). */
103
+ repo?: string;
104
+ /** Companion alert badge (count + latest, themed by tier). */
105
+ alerts?: AlertBadge;
106
+ /** A live `alert`-tier badge takes ⏎ until it is acknowledged (Esc) or
107
+ * followed into the companion tab. */
108
+ alertFocus?: boolean;
109
+ /** Active board filter (companion `board_filter` verb; F clears).
110
+ * Consumed through `visiblePrs` — never filter a sortPrs call directly. */
111
+ boardFilter?: BoardFilter | null;
112
+ }
113
+
114
+ // ---- layout ----------------------------------------------------------------
115
+
116
+ /** "held by the operator" / "held by a session, lapses 12:30:00Z";
117
+ * an older daemon reports no holder, and that is said rather than guessed. */
118
+ export function leverHolderLabel(
119
+ by: "operator" | "session" | undefined,
120
+ until: string | undefined,
121
+ ): string {
122
+ if (by === undefined) return "holder unknown (older daemon)";
123
+ const who = by === "operator" ? "held by the operator" : "held by a session";
124
+ return until ? `${who}, lapses ${until.slice(11, 19)}Z` : `${who} until lifted`;
125
+ }
126
+
127
+ /** The drain bar's hold marker: ` · HOLD …` for the operator's no-auto-lift
128
+ * drain, ` · hold ?` for an operator drain from a daemon that reports no
129
+ * marker (honest unknown), nothing otherwise. */
130
+ export function drainHoldLabel(s: Pick<StatusSnapshot, "draining_by" | "draining_hold">): string {
131
+ if (s.draining_hold === true) return " · HOLD: no boot lifts it, drain off does";
132
+ if (s.draining_hold === undefined && s.draining_by === "operator") return " · hold ?";
133
+ return "";
134
+ }
135
+
136
+ /** The pause chip on a PR row: `⏸op` for the operator's hold, `⏸` + the lapse time for a session's. */
137
+ function pauseChip(pr: SnapshotPr): string {
138
+ if (!pr.paused) return "";
139
+ if (!pr.pause) return "⏸";
140
+ if (pr.pause.issuer === "operator") return "⏸op";
141
+ return pr.pause.expires_at ? `⏸→${pr.pause.expires_at.slice(11, 16)}Z` : "⏸";
142
+ }
143
+
144
+ /** A PR row's verdict chip: a held review failure, else the last verdict. */
145
+ function rowChip(pr: SnapshotPr): string {
146
+ if (pr.review_failure) return `${FG.anomaly}FAILED_LOUD${RESET}`;
147
+ // The worker crash-loop breaker parked it: no worker starts until rerun-round / restart-session.
148
+ if (pr.worker_crash?.parked_at) return `${FG.anomaly}PARKED${RESET}`;
149
+ return verdictChip(pr.last_verdict);
150
+ }
151
+
152
+ /** A PR title as one inert line — it is the author's text, and the row is
153
+ * written straight to the terminal. */
154
+ function rowTitle(pr: SnapshotPr): string {
155
+ return oneLine(pr.title);
156
+ }
157
+
158
+ /** A PR row as `styledRow` paints it, without its styling — what the list
159
+ * is sized to. */
160
+ function rowPlain(pr: SnapshotPr, now: Date): string {
161
+ const rounds = pr.rounds ? `r${pr.rounds}` : "";
162
+ return [
163
+ glyphFor(pr),
164
+ `#${pr.number}`,
165
+ pr.author ?? "",
166
+ fmtAge(pr.updated_at, now),
167
+ rounds,
168
+ stripAnsi(rowChip(pr)),
169
+ pauseChip(pr),
170
+ rowTitle(pr),
171
+ ]
172
+ .filter((s) => s !== "")
173
+ .join(" ");
174
+ }
175
+
176
+ /** Left column sized to the longest PR row so titles aren't cropped while
177
+ * the detail pane sits empty — clamped to 45% of the terminal. */
178
+ export function listWidth(prs: SnapshotPr[], cols: number, now: Date = new Date(0)): number {
179
+ const widest = prs.reduce(
180
+ (w, pr) => Math.max(w, displayWidth(rowPlain(pr, now))),
181
+ displayWidth(stripAnsi(legendLine())),
182
+ );
183
+ return Math.min(Math.max(24, widest + 3), Math.floor(cols * 0.45));
184
+ }
185
+
186
+ /**
187
+ * What the board answers to on this frame (the bar is state-derived, so a
188
+ * button is painted exactly when its key would do something).
189
+ *
190
+ * The verbs that act on the selected PR appear only while there is one: with
191
+ * no snapshot, or an empty list, a `[u unwedge]` would be an action derived
192
+ * from no data — the second half of the honest-unknown rule. `g` also needs
193
+ * the repo slug the URL is built from. `e` toggles the events overlay
194
+ * regardless, and `I` (setup) is about the repo, not a PR. Inline: `a` is ⏎'s
195
+ * printable twin and `F` clears a filter the PRS subtitle is advertising.
196
+ */
197
+ export function boardKeys(
198
+ view: Pick<BoardView, "snapshot" | "boardFilter" | "selected" | "repo">,
199
+ ): LiveKeys {
200
+ const selected = visiblePrs(view)[view.selected] !== undefined;
201
+ const bar: BarButton[] = [];
202
+ if (selected) bar.push({ key: "\r", label: "⏎ pi pane" }, { key: "m", label: "m message" });
203
+ if (selected && view.repo) bar.push({ key: "g", label: "g github" });
204
+ bar.push({ key: "e", label: "e events" });
205
+ if (selected) bar.push({ key: "u", label: "u unwedge" });
206
+ // Setup is about the repo, not a PR — offered whatever is selected.
207
+ bar.push({ key: "I", label: "I setup" });
208
+ const inline: string[] = [];
209
+ if (selected) inline.push("a");
210
+ if (view.boardFilter != null) inline.push("F");
211
+ // A worker-hosted PR's pane is the transcript viewer: `[ ]` step its streams
212
+ // and `< >` its rounds. They are the pane header's keys (painted there), so
213
+ // they exist only while the selected PR's pane is the viewer.
214
+ const pr = visiblePrs(view)[view.selected];
215
+ if (pr !== undefined && paneSurface(pr.runtime, pr.number).kind === "viewer")
216
+ inline.push("[", "]", "<", ">");
217
+ return { bar, inline };
218
+ }
219
+
220
+ /** Rows the command bar takes on the board at this width — the one number
221
+ * `renderBoard` and the App's pi-viewport arithmetic must agree on. */
222
+ export function boardBarHeight(
223
+ view: Pick<BoardView, "snapshot" | "boardFilter" | "selected" | "repo">,
224
+ cols: number,
225
+ ): number {
226
+ return barHeight("board", cols, boardKeys(view).bar);
227
+ }
228
+
229
+ /**
230
+ * The live pi mirror owns the bottom half of the board. Two rows are chrome
231
+ * (divider + header), the rest is session tail.
232
+ * Floors at 6 rows and never squeezes the list/detail body below 3.
233
+ */
234
+ export function piRows(rows: number, barRows: number): number {
235
+ const usable = rows - 2 - barRows; // top bar + command bar + bottom border
236
+ return Math.max(0, Math.min(Math.max(6, Math.floor(usable * 0.5)), usable - 3));
237
+ }
238
+
239
+ /**
240
+ * The exact terminal geometry the mirrored pi session should have — the
241
+ * rectangle `renderBoard` paints its capture into, and nothing more.
242
+ *
243
+ * One source of truth on purpose, shared with the paint below: if these numbers
244
+ * drift, pi lays itself out for a terminal of a different size than the one its
245
+ * output is rendered in, which is the bug this exists to stop. Two of the pane's
246
+ * rows are chrome (divider + header); of the width, two cells are the board's
247
+ * borders and one is the leading space each mirror row is painted with.
248
+ */
249
+ export function piPaneSize(
250
+ cols: number,
251
+ rows: number,
252
+ barRows: number,
253
+ ): { cols: number; rows: number } | undefined {
254
+ const lines = piRows(rows, barRows) - 2;
255
+ const width = cols - 4;
256
+ if (lines <= 0 || width <= 0) return undefined;
257
+ return { cols: width, rows: lines };
258
+ }
259
+
260
+ /** Geometry of the floating EVENTS overlay: a centered band over the board,
261
+ * bordered top and bottom, ~60% of the height, never under the bar. */
262
+ export function eventsOverlayBand(rows: number, barRows: number): { top: number; height: number } {
263
+ const height = Math.max(5, Math.min(Math.floor(rows * 0.6), rows - 3 - barRows));
264
+ return { top: Math.max(1, Math.floor((rows - height) / 2)), height };
265
+ }
266
+
267
+ // ---- pieces ----------------------------------------------------------------
268
+
269
+ /** The companion's alert badge as it sits in the top bar: tier color, today's
270
+ * count, the latest title, and how many repeats folded into counters. */
271
+ export function alertSegment(badge: AlertBadge, focused: boolean): string {
272
+ const latest = badge.latest;
273
+ if (!latest) return "";
274
+ const color = latest.tier === "alert" ? FG.anomaly : FG.accent;
275
+ const glyph = latest.tier === "alert" ? "▲" : "•";
276
+ const where = latest.pr !== null ? ` #${latest.pr}` : "";
277
+ const folds = badge.folds > 0 ? ` +${badge.folds}` : "";
278
+ const label = `${glyph} ${badge.count}${where} ${truncate(latest.title, 40)}${folds}${focused ? " ⏎ companion" : ""}`;
279
+ return focused ? selectedLine(`${color}${label}${RESET}`) : `${color}${label}${RESET}`;
280
+ }
281
+
282
+ /** Short top-bar label per triage class — the long form lives in the list. */
283
+ const CONNECTION_LABEL: Record<ConnectionDiagnosis["kind"], string> = {
284
+ ok: "ssh ok",
285
+ local: "local",
286
+ "no-agent-key": "no ssh key loaded",
287
+ "wedged-master": "ssh master wedged",
288
+ "host-unreachable": "host down",
289
+ unknown: "cause unknown",
290
+ };
291
+
292
+ /**
293
+ * What the board says where the PR list would be, when it has never had a
294
+ * snapshot.
295
+ *
296
+ * The count is `?`, never `0` (an empty count reads as "the data is gone" when
297
+ * the truth is "I could not ask"), the reason is named, and the remedy is a
298
+ * command the operator can paste into their own shell.
299
+ */
300
+ export function unknownSourceLines(conn: ConnectionDiagnosis | undefined, width: number): string[] {
301
+ const lines = [
302
+ `${FG.anomaly}the daemon has not answered yet${RESET}`,
303
+ `${FG.dim}nothing below is a measurement — it is not known${RESET}`,
304
+ "",
305
+ ];
306
+ if (conn && conn.kind !== "ok" && conn.kind !== "local") {
307
+ for (const l of wrap(stripAnsi(conn.cause), width)) lines.push(`${FG.dim}${l}${RESET}`);
308
+ if (conn.remedy !== "") {
309
+ lines.push("");
310
+ lines.push(`${STRONG}run this:${RESET}`);
311
+ for (const l of wrap(conn.remedy, width)) lines.push(`${FG.accent} ${l}${RESET}`);
312
+ // The remedy is a shell command, and the operator is inside the alt
313
+ // screen looking at it. For the one class `s` can clear in place — a key
314
+ // that is simply not loaded — say so here rather than leaving it to the
315
+ // `?` key map: otherwise the honest answer reads as "quit the TUI first".
316
+ if (conn.kind === "no-agent-key") {
317
+ for (const l of wrap("…or press s to be prompted for the key passphrase here", width))
318
+ lines.push(`${FG.dim}${l}${RESET}`);
319
+ }
320
+ }
321
+ } else if (!conn) {
322
+ lines.push(`${FG.dim}checking the connection…${RESET}`);
323
+ }
324
+ // Bring-up progress is not painted here: the companion column is on every
325
+ // frame and carries it (sidebar.ts `startup`).
326
+ lines.push("");
327
+ lines.push(`${FG.dim}s re-checks the connection · r refreshes${RESET}`);
328
+ return lines.map((l) => truncate(l, width));
329
+ }
330
+
331
+ /** The detail pane's twin of the above: never "no PR selected" (which implies
332
+ * there were PRs to select from) when the list itself is unknown. */
333
+ export function unknownDetailLines(conn: ConnectionDiagnosis | undefined, width: number): string[] {
334
+ const lines = [
335
+ `${STRONG}no data${RESET}`,
336
+ `${FG.dim}the TUI has not been able to read status.json${RESET}`,
337
+ ];
338
+ if (conn?.detail) {
339
+ lines.push("");
340
+ for (const l of wrap(`ssh said: ${stripAnsi(conn.detail)}`, width))
341
+ lines.push(`${FG.dim}${l}${RESET}`);
342
+ }
343
+ return lines.map((l) => truncate(l, width));
344
+ }
345
+
346
+ /**
347
+ * The board's reviewer-memory banner, from status.json `memory` and nothing
348
+ * else — never from OpenViking, which is the thing that may be down. The
349
+ * memory tab [2] only shows what the TUI's own store can reach, so this is
350
+ * where the daemon's view of memory health reaches a person.
351
+ *
352
+ * `loud` — down / refusing a key / degraded: painted right after the
353
+ * daemon health, where truncation reaches it last;
354
+ * `quiet` — not measured yet, not reported by this daemon, not monitored:
355
+ * a short dim mark at the end of the bar — honest, never healthy,
356
+ * and the first thing a narrow terminal drops;
357
+ * nothing — healthy, or no snapshot at all (the whole board already says
358
+ * it does not know).
359
+ */
360
+ export function memorySegment(
361
+ snapshot: StatusSnapshot | undefined,
362
+ now: Date,
363
+ ): { text: string; loud: boolean } | undefined {
364
+ if (!snapshot) return undefined;
365
+ const m = snapshot.memory;
366
+ if (!m) return { text: `${FG.dim}mem ?${RESET}`, loud: false };
367
+ if (!m.enabled)
368
+ return {
369
+ text: `${FG.dim}${m.disabled_reason === MEMORY_OFF_NO_DOCKER ? MEMORY_OFF_NO_DOCKER : "mem off"}${RESET}`,
370
+ loud: false,
371
+ };
372
+ const since = Date.parse(m.incident?.since ?? m.since);
373
+ const age = Number.isFinite(since) ? ` ${fmtDuration(now.getTime() - since)}` : "";
374
+ switch (m.state) {
375
+ case "healthy":
376
+ return undefined;
377
+ case "unknown":
378
+ return { text: `${FG.dim}mem ?${RESET}`, loud: false };
379
+ case "degraded":
380
+ return {
381
+ // Memory that answers but cannot search says so.
382
+ text: `${WARN}▲ MEMORY DEGRADED${age}${m.serving?.ok === false ? " · search not serving" : ""}${DRESET}`,
383
+ loud: true,
384
+ };
385
+ case "down":
386
+ return {
387
+ text: `${FG.anomaly}⛔ MEMORY DOWN${age} · ${m.down_cause === "not_serving" ? "search not serving" : "no authenticated answer"}${RESET}`,
388
+ loud: true,
389
+ };
390
+ case "auth_failed": {
391
+ const d = isAuthResult(m.credentials.daemon);
392
+ const sess = isAuthResult(m.credentials.sessions);
393
+ const which = d && sess ? "both keys" : d ? "daemon key" : sess ? "sessions' key" : "a key";
394
+ return {
395
+ text: `${FG.anomaly}⛔ MEMORY AUTH FAILED${age} · ${which} refused${RESET}`,
396
+ loud: true,
397
+ };
398
+ }
399
+ }
400
+ }
401
+
402
+ /**
403
+ * The board's EOD-curation banner, from status.json
404
+ * `memory.curation` alone: loud when the day's curation did not reach memory
405
+ * — no receipt (`missing`), or a lesson refused or not found
406
+ * (`not_persisted`). Both are also overseer wakes; this is the half a person
407
+ * sees, beside the memory banner. A block the daemon could not read (`null`)
408
+ * is a dim `curation ?` — honest-unknown: painting nothing would read the
409
+ * same as a quiet day, which `qcr status` and the companion's view both call
410
+ * unknown. Nothing for a daemon that reports no block (`undefined`: memory
411
+ * off, or an older daemon), and a quiet day is quiet.
412
+ */
413
+ export function curationSegment(snapshot: StatusSnapshot | undefined): string | undefined {
414
+ const c = snapshot?.memory?.curation;
415
+ if (c === null && snapshot?.memory) return `${FG.dim}curation ?${RESET}`;
416
+ if (!curationNeedsAttention(c) || !c) return undefined;
417
+ return c.state === "missing"
418
+ ? `${FG.anomaly}⛔ EOD CURATION MISSING ${c.day ?? "?"} · ${c.offered} candidate${c.offered === 1 ? "" : "s"}, no receipt${earlierPhrase(c)}${RESET}`
419
+ : `${FG.anomaly}⛔ LESSONS NOT PERSISTED ${c.day ?? "?"} · ${c.not_persisted}${earlierPhrase(c)}${RESET}`;
420
+ }
421
+
422
+ /**
423
+ * The board's archive banner (docs/archiving.md §11), from
424
+ * status.json `archive` alone, through its total reader: loud only for what
425
+ * needs a person (`archiveAttention`) — held for disk, a restore that failed,
426
+ * a reopened PR no tick or birth will restore (mode not `on`: its archived
427
+ * receipts read unknown until `qcr archive restore`), or repeated verify
428
+ * failures. A block this build cannot read is a dim `archive ?`, never
429
+ * silence; nothing for a daemon that reports no block, and a quiet archiver
430
+ * is quiet.
431
+ */
432
+ export function archiveSegment(snapshot: StatusSnapshot | undefined): string | undefined {
433
+ if (!snapshot) return undefined;
434
+ const r = readArchiveStatus(snapshot.archive);
435
+ if (r.kind === "absent") return undefined;
436
+ if (r.kind === "unreadable") return `${FG.dim}archive ?${RESET}`;
437
+ const parts = archiveAttention(r.view);
438
+ return parts.length > 0 ? `${FG.anomaly}⛔ ARCHIVE ${parts.join(" · ")}${RESET}` : undefined;
439
+ }
440
+
441
+ /**
442
+ * The board's event-loop banner, from status.json `daemon` and nothing else:
443
+ * the daemon measuring its own loop, so a stalled loop (an unanswered /health)
444
+ * is visible on the board.
445
+ *
446
+ * `loud` — stalled: painted beside the memory banner, with the cause the
447
+ * evidence names (the loop itself blocked / CPU starved /
448
+ * cause unknown);
449
+ * `quiet` — not measured yet, not reported by this daemon (an older one),
450
+ * or a block this build cannot read whole (a newer daemon's state,
451
+ * a missing field): a short dim `loop ?` at the end of the bar —
452
+ * honest, never ok;
453
+ * nothing — ok, or no snapshot at all.
454
+ *
455
+ * The block is parsed JSON, read through `readDaemonLoad`: nothing in it can
456
+ * throw out of a paint.
457
+ */
458
+ export function eventLoopSegment(
459
+ snapshot: StatusSnapshot | undefined,
460
+ ): { text: string; loud: boolean } | undefined {
461
+ if (!snapshot) return undefined;
462
+ const r = readDaemonLoad(snapshot.daemon);
463
+ if (r.kind !== "read" || r.view.event_loop.state === "unknown")
464
+ return { text: `${FG.dim}loop ?${RESET}`, loud: false };
465
+ if (r.view.event_loop.state === "ok") return undefined;
466
+ return { text: `${FG.anomaly}⛔ ${stallShortPhrase(snapshot.daemon)}${RESET}`, loud: true };
467
+ }
468
+
469
+ /** Top-bar segments, left to right — shared by the renderer and the hit-test
470
+ * geometry so the alert click zone can never drift from the text. */
471
+ function barSegments(view: BoardView, alerts: boolean): string[] {
472
+ const mode = modeBadge(view.dryRun);
473
+ let health: string;
474
+ switch (view.health.kind) {
475
+ case "ok":
476
+ health = `${FG.approve}●${RESET} daemon ok (hb ${view.health.heartbeatAgeS}s)`;
477
+ break;
478
+ case "pipeline_unhealthy":
479
+ health = `${FG.anomaly}⛔ daemon alive · PIPELINE FAILED · ${view.health.causes.length} (hb ${view.health.heartbeatAgeS}s)${RESET}`;
480
+ break;
481
+ case "stale":
482
+ health = `${WARN}▲ DATA STALE (${view.health.dataAgeS}s)${DRESET}`;
483
+ break;
484
+ case "unreachable":
485
+ health = `${FG.anomaly}✖ unreachable${view.connection ? ` · ${CONNECTION_LABEL[view.connection.kind]}` : ""}${RESET}`;
486
+ break;
487
+ }
488
+ const s = view.snapshot;
489
+ const rounds = s ? `rounds ${s.running_rounds}/${s.max_concurrent_rounds ?? "?"}` : "";
490
+ const spend = s?.today_spend_usd !== undefined ? `today $${s.today_spend_usd.toFixed(2)}` : "";
491
+ // Deploy-window drain: while the daemon drains, the bar says so loudly,
492
+ // with how many rounds still have to finish before a deploy is safe and how
493
+ // many webhook deliveries were dropped so far — and by whom, and when it
494
+ // lapses: an operator's drain holds until lifted, a session's always has an
495
+ // expiry. An older daemon that reports neither is shown as such rather than
496
+ // guessed at. The operator's HOLD says that no boot lifts it; an
497
+ // operator drain from a daemon that reports no hold marker says "hold ?".
498
+ const drainSeg = s?.draining
499
+ ? `${FG.anomaly}⛔ DRAINING — ${s.running_rounds} round${s.running_rounds === 1 ? "" : "s"} finishing · ${s.drain_failed_loud ?? 0} failed loud · ${s.drained_deliveries ?? 0} dropped · ${leverHolderLabel(s.draining_by, s.draining_until)}${drainHoldLabel(s)}${s.draining_reason === "worker_failures" ? " · workers keep failing, drain off lifts it" : ""}${RESET}`
500
+ : "";
501
+ const td = s?.thread_decisions;
502
+ const breakerSeg =
503
+ td && (td.breaker_state !== "closed" || td.failed > 0)
504
+ ? `${FG.anomaly}⛔ THREAD RETRY ${td.breaker_state.toUpperCase()}${td.open_until ? ` until ${td.open_until.slice(11, 19)}Z` : ""} · ${td.pending} pending · ${td.failed} failed${RESET}`
505
+ : "";
506
+ const badge = alerts && view.alerts ? alertSegment(view.alerts, view.alertFocus === true) : "";
507
+ const mem = memorySegment(s, view.now ?? new Date());
508
+ const curation = curationSegment(s) ?? "";
509
+ const archive = archiveSegment(s) ?? "";
510
+ const loop = eventLoopSegment(s);
511
+ return [
512
+ `${HEADING}quickreview${DRESET}`,
513
+ mode,
514
+ health,
515
+ mem?.loud ? mem.text : "",
516
+ loop?.loud ? loop.text : "",
517
+ curation,
518
+ archive,
519
+ drainSeg,
520
+ breakerSeg,
521
+ rounds,
522
+ spend,
523
+ badge,
524
+ // Only when `alerts` — the hit-test geometry measures the segments BEFORE
525
+ // the badge, and a quiet mark sits after it.
526
+ alerts && mem && !mem.loud ? mem.text : "",
527
+ alerts && loop && !loop.loud ? loop.text : "",
528
+ ].filter((x) => x !== "");
529
+ }
530
+
531
+ export function topBar(view: BoardView, cols: number): string {
532
+ return rule(cols, "┌", "┐", barSegments(view, true));
533
+ }
534
+
535
+ /**
536
+ * 1-based screen columns of the alert badge inside the top bar, or undefined
537
+ * when there is no badge. Derived from the same segment list `topBar` renders:
538
+ * `rule` lays out `border + " " + segments.join(" ── ") + " "`, so the badge
539
+ * starts one column past the border, the leading space, and every earlier
540
+ * segment.
541
+ */
542
+ export function alertZone(view: BoardView, cols: number): { x1: number; x2: number } | undefined {
543
+ if (!view.alerts?.latest) return undefined;
544
+ const before = barSegments(view, false);
545
+ const prefix = before.length > 0 ? `${before.join(RULE_SEGMENT_SEP)}${RULE_SEGMENT_SEP}` : "";
546
+ const x1 = 3 + displayWidth(stripAnsi(prefix));
547
+ const width = displayWidth(stripAnsi(alertSegment(view.alerts, view.alertFocus === true)));
548
+ if (x1 >= cols) return undefined; // truncated away on a narrow terminal
549
+ return { x1, x2: Math.min(cols - 1, x1 + width - 1) };
550
+ }
551
+
552
+ function styledRow(
553
+ pr: SnapshotPr,
554
+ selected: boolean,
555
+ width: number,
556
+ now: Date,
557
+ repo: string | undefined,
558
+ ): string {
559
+ const color = stateColor(pr.stale_recheck ? "reviewing" : pr.state);
560
+ const chip = rowChip(pr);
561
+ const rounds = pr.rounds ? `${FG.dim}r${pr.rounds}${RESET}` : "";
562
+ const paused = pr.paused ? `${WARN}${pauseChip(pr)}${DRESET}` : "";
563
+ const head = `${color}${glyphFor(pr)} ${STRONG}#${pr.number}${RESET}`;
564
+ const meta = [
565
+ `${FG.author}${pr.author ?? ""}${RESET}`,
566
+ `${FG.dim}${fmtAge(pr.updated_at, now)}${RESET}`,
567
+ rounds,
568
+ chip,
569
+ paused,
570
+ ]
571
+ .filter((s) => stripAnsi(s) !== "")
572
+ .join(" ");
573
+ const used = displayWidth(`${stripAnsi(head)} ${stripAnsi(meta)} `);
574
+ const title = truncate(rowTitle(pr), Math.max(4, width - used - 1));
575
+ const numLinked = repo
576
+ ? `${color}${glyphFor(pr)} ${STRONG}${osc8(prUrl(repo, pr.number), `#${pr.number}`)}${RESET}`
577
+ : head;
578
+ const body = `${numLinked} ${meta} ${title}`;
579
+ const pad = " ".repeat(Math.max(0, width - displayWidth(body) - 1));
580
+ return selected ? selectedLine(` ${body}${pad}`) : ` ${body}${pad}`;
581
+ }
582
+
583
+ const VERDICT_ABBREV: Record<string, string> = {
584
+ REQUEST_CHANGES: "RC",
585
+ APPROVE: "APPROVE",
586
+ COMMENT: "COMMENT",
587
+ };
588
+
589
+ function section(title: string, subtitle: string): string {
590
+ return sectionTitle(title, subtitle);
591
+ }
592
+
593
+ function durationLabel(ms: number): string {
594
+ const seconds = Math.max(0, Math.floor(ms / 1_000));
595
+ if (seconds < 60) return `${seconds}s`;
596
+ const minutes = Math.floor(seconds / 60);
597
+ if (minutes < 60) return `${minutes}m`;
598
+ const hours = Math.floor(minutes / 60);
599
+ return `${hours}h${minutes % 60 > 0 ? ` ${minutes % 60}m` : ""}`;
600
+ }
601
+
602
+ function oneLine(value: string): string {
603
+ return sanitizeText(value).replace(/\s+/g, " ").trim();
604
+ }
605
+
606
+ type CurrentRound = Pick<
607
+ WhyPayload["rounds"][number],
608
+ "round_no" | "status" | "started_at" | "deadline_at" | "lenses" | "validate_tier"
609
+ > & {
610
+ command_id?: string;
611
+ command_sha256?: string;
612
+ head_sha?: string;
613
+ stage_deadlines?: {
614
+ first_pass_start: string;
615
+ passes_complete: string;
616
+ running_hard_bound: string;
617
+ };
618
+ };
619
+
620
+ /** A lens row of the progress panel, by its line in the detail: what a click on
621
+ * it selects in the viewer (the `nth` run of `lens` the round started). */
622
+ export interface ProgressRow {
623
+ line: number;
624
+ lens: string;
625
+ nth: number;
626
+ }
627
+
628
+ function currentProgressLines(
629
+ round: CurrentRound,
630
+ progress: RoundProgressSnapshot | undefined,
631
+ width: number,
632
+ now: Date,
633
+ rowsOut?: ProgressRow[],
634
+ firstLine = 0,
635
+ ): string[] {
636
+ const lines: string[] = [];
637
+ const started = Date.parse(round.started_at);
638
+ const deadline = round.deadline_at ? Date.parse(round.deadline_at) : Number.NaN;
639
+ const nowMs = now.getTime();
640
+ const elapsed = Number.isFinite(started) ? durationLabel(nowMs - started) : "?";
641
+ const deadlineText = Number.isFinite(deadline)
642
+ ? deadline >= nowMs
643
+ ? `${durationLabel(deadline - nowMs)} left`
644
+ : `${durationLabel(nowMs - deadline)} OVERDUE`
645
+ : "deadline unavailable";
646
+ lines.push(
647
+ truncate(
648
+ section("CURRENT REVIEW", `round ${round.round_no} · ${elapsed} elapsed · ${deadlineText}`),
649
+ width,
650
+ ),
651
+ );
652
+
653
+ // A sticky session may process neighboring wakes at nearly identical times.
654
+ // Only the exact durable delivery + prompt-derived round/head identity may
655
+ // paint this round; timestamp proximity is not identity.
656
+ const current =
657
+ progress?.version === 2 &&
658
+ round.command_id !== undefined &&
659
+ progress.command_id === round.command_id &&
660
+ round.command_sha256 !== undefined &&
661
+ progress.command_sha256 === round.command_sha256 &&
662
+ round.head_sha !== undefined &&
663
+ progress.head_sha === round.head_sha &&
664
+ progress.round_no === round.round_no
665
+ ? progress
666
+ : undefined;
667
+ const planned = round.lenses
668
+ .split(",")
669
+ .map((x) => x.trim())
670
+ .filter(Boolean);
671
+ // The validator runs at its own tier, shown beside its row.
672
+ const tierOf = (name: string): string =>
673
+ name === "pass-validate" && round.validate_tier ? ` · ${round.validate_tier}` : "";
674
+
675
+ if (!current) {
676
+ lines.push(truncate(` ${FG.dim}? parent · live progress unavailable${RESET}`, width));
677
+ for (const name of planned) {
678
+ lines.push(truncate(` ${FG.dim}? ${name} · unknown${tierOf(name)}${RESET}`, width));
679
+ }
680
+ lines.push(truncate(` ${FG.dim}? final verdict · unknown${RESET}`, width));
681
+ return lines;
682
+ }
683
+
684
+ const signalAge = Math.max(0, nowMs - Date.parse(current.updated_at));
685
+ const signal =
686
+ signalAge >= 60_000
687
+ ? `SILENT ${durationLabel(signalAge)}`
688
+ : `signal ${durationLabel(signalAge)} ago`;
689
+ const parentColor =
690
+ signalAge >= 120_000 ? FG.anomaly : current.parent_status === "active" ? FG.accent : FG.dim;
691
+ const active = current.active_tool ? ` · ${oneLine(current.active_tool)}` : "";
692
+ const stage = roundExecutionStage(current, planned);
693
+ const stageDeadline =
694
+ stage === "accepted" || stage === "preparing"
695
+ ? round.stage_deadlines?.first_pass_start
696
+ : stage === "fanout-started"
697
+ ? round.stage_deadlines?.passes_complete
698
+ : undefined;
699
+ const due = stageDeadline
700
+ ? ` · stage ${Date.parse(stageDeadline) >= nowMs ? `due in ${durationLabel(Date.parse(stageDeadline) - nowMs)}` : `OVERDUE ${durationLabel(nowMs - Date.parse(stageDeadline))}`}`
701
+ : "";
702
+ lines.push(
703
+ truncate(
704
+ ` ${parentColor}parent ${current.parent_status.toUpperCase()} · ${stage}${due} · ${signal}${active}${RESET}`,
705
+ width,
706
+ ),
707
+ );
708
+
709
+ const agentLine = (name: string, agent: LiveAgentProgress | undefined): string => {
710
+ const tier = tierOf(name);
711
+ if (!agent) return ` ${FG.dim}○ ${name} · pending${tier}${RESET}`;
712
+ if (agent.status === "running") {
713
+ const tool = agent.current_tool ? ` · ${oneLine(agent.current_tool)}` : "";
714
+ return ` ${FG.accent}⟳ ${name} · running ${durationLabel(agent.duration_ms)}${tool}${tier}${RESET}`;
715
+ }
716
+ if (agent.status === "failed") {
717
+ const error = agent.error ? ` · ${oneLine(agent.error)}` : "";
718
+ return ` ${FG.anomaly}✗ ${name} · failed after ${durationLabel(agent.duration_ms)}${error}${tier}${RESET}`;
719
+ }
720
+ return ` ${FG.approve}✓ ${name} · done in ${durationLabel(agent.duration_ms)} · ${agent.tool_count} tools${tier}${RESET}`;
721
+ };
722
+ // Every execution of a planned lens is shown — a lens run twice (two task
723
+ // rows) never hides a running or failed sibling behind a finished one.
724
+ const runs = new Map<string, LiveAgentProgress[]>();
725
+ for (const agent of current.agents) {
726
+ const name = lensForAgent(agent.agent);
727
+ runs.set(name, [...(runs.get(name) ?? []), agent]);
728
+ }
729
+ for (const name of new Set(planned)) {
730
+ const executions = runs.get(name) ?? [];
731
+ const rows = Math.max(planned.filter((p) => p === name).length, executions.length);
732
+ for (let i = 0; i < rows; i++) {
733
+ rowsOut?.push({ line: firstLine + lines.length, lens: name, nth: i });
734
+ lines.push(truncate(agentLine(name, executions[i]), width));
735
+ }
736
+ }
737
+ // The parent may justify and add a lens outside the dispatch plan. Those
738
+ // agents are real work too; omitting them would make "all agents" false.
739
+ const added = new Map<string, number>();
740
+ for (const agent of current.agents.filter(
741
+ (item) => !planned.includes(lensForAgent(item.agent)),
742
+ )) {
743
+ const lens = lensForAgent(agent.agent);
744
+ const nth = added.get(lens) ?? 0;
745
+ added.set(lens, nth + 1);
746
+ rowsOut?.push({ line: firstLine + lines.length, lens, nth });
747
+ const rendered = agentLine(agent.agent, agent).replace(`${RESET}`, ` · added${RESET}`);
748
+ lines.push(truncate(rendered, width));
749
+ }
750
+ lines.push(truncate(` ${FG.dim}○ final verdict · pending${RESET}`, width));
751
+ const activityAge = Math.max(0, nowMs - Date.parse(current.last_activity_at));
752
+ lines.push(
753
+ truncate(
754
+ ` ${FG.dim}last activity ${durationLabel(activityAge)} ago · ${oneLine(current.last_activity)}${RESET}`,
755
+ width,
756
+ ),
757
+ );
758
+ if (signalAge >= 120_000) {
759
+ lines.push(
760
+ truncate(
761
+ ` ${FG.anomaly}⚠ no live signal for ${durationLabel(signalAge)} — inspect raw PI below${RESET}`,
762
+ width,
763
+ ),
764
+ );
765
+ }
766
+ return lines;
767
+ }
768
+
769
+ /** Doubted findings shown before the "… N more" line. */
770
+ const DOUBTED_SHOWN = 8;
771
+
772
+ /**
773
+ * The DOUBTED section: findings the validator could not stand behind
774
+ * — kept in the findings record as "may not be valid", never posted on the PR,
775
+ * and put here for the operator to judge. Newest round first. Nothing renders
776
+ * when there are none, or when an older daemon did not report the field.
777
+ */
778
+ export function doubtedLines(
779
+ doubted: WhyPayload["doubted"],
780
+ width: number,
781
+ title = "DOUBTED",
782
+ note = "may not be valid · kept, never posted",
783
+ ): string[] {
784
+ if (!doubted || doubted.length === 0) return [];
785
+ const lines = [truncate(section(title, note), width)];
786
+ const newest = [...doubted].sort((a, b) => b.round_no - a.round_no);
787
+ for (const f of newest.slice(0, DOUBTED_SHOWN)) {
788
+ const at = f.path ? ` ${sanitizeText(f.path)}${f.line !== null ? `:${f.line}` : ""}` : "";
789
+ const sev = f.severity ? ` · ${sanitizeText(f.severity)}` : "";
790
+ const word = f.word ? ` · ${sanitizeText(f.word)}` : "";
791
+ const why = f.reason ? ` — ${oneLine(f.reason)}` : "";
792
+ lines.push(
793
+ truncate(
794
+ ` ${WARN}? r${f.round_no} ${sanitizeText(f.lens)}${at}${sev}${word}${DRESET}${FG.dim}${why}${RESET}`,
795
+ width,
796
+ ),
797
+ );
798
+ }
799
+ if (newest.length > DOUBTED_SHOWN)
800
+ lines.push(`${FG.dim} … ${newest.length - DOUBTED_SHOWN} more (qcr why <pr> --json)${RESET}`);
801
+ return lines;
802
+ }
803
+
804
+ export function detailLines(
805
+ detail: WhyPayload | undefined,
806
+ pr: SnapshotPr | undefined,
807
+ width: number,
808
+ now: Date = new Date(0),
809
+ progress?: RoundProgressSnapshot,
810
+ progressRows?: ProgressRow[],
811
+ ): string[] {
812
+ const lines: string[] = [];
813
+ if (!pr) return ["no PR selected"];
814
+ lines.push(
815
+ `${stateColor(pr.state)}${STRONG}#${pr.number}${RESET} ${STRONG}${truncate(sanitizeText(pr.title), width - 8 - String(pr.number).length)}${RESET}`,
816
+ );
817
+ const delivery = pr.current_round?.posting_delivery;
818
+ if (delivery) {
819
+ lines.push(
820
+ truncate(
821
+ `${FG.dim}POSTING PENDING · automatic retry · attempts ${delivery.attempts}${RESET}`,
822
+ width,
823
+ ),
824
+ );
825
+ lines.push(
826
+ truncate(
827
+ `next ${sanitizeText(delivery.next_attempt_at)}${delivery.last_error ? ` · ${oneLine(delivery.last_error)}` : ""}`,
828
+ width,
829
+ ),
830
+ );
831
+ }
832
+ if (!detail || detail.pr !== pr.number) {
833
+ lines.push(`${FG.dim}loading detail…${RESET}`);
834
+ return lines;
835
+ }
836
+ const round =
837
+ pr.current_round?.round_no ??
838
+ (detail.rounds.length > 0 ? detail.rounds[detail.rounds.length - 1]?.round_no : undefined);
839
+ const facts: string[] = [
840
+ `${stateColor(detail.state ?? pr.state)}${detail.state ?? pr.state}${RESET}`,
841
+ ];
842
+ if (round !== undefined) facts.push(`round ${round}`);
843
+ if (detail.author) facts.push(`by ${FG.author}${detail.author}${RESET}`);
844
+ lines.push(truncate(facts.join(" · "), width));
845
+ const trig = detail.trigger_ts ? `trig ${fmtAge(detail.trigger_ts, now)} ago` : "trig —";
846
+ lines.push(
847
+ truncate(
848
+ `spend $${detail.spend_usd.toFixed(2)} · head ${(detail.head_sha ?? "").slice(0, 7)} · ${trig}`,
849
+ width,
850
+ ),
851
+ );
852
+ if (detail.labels && detail.labels.length > 0) {
853
+ lines.push(
854
+ truncate(
855
+ detail.labels.map((l) => `${FG.accent}[${sanitizeText(l)}]${RESET}`).join(" "),
856
+ width,
857
+ ),
858
+ );
859
+ }
860
+ if (pr.paused) {
861
+ // A held PR says who holds it and when the hold lapses.
862
+ const hold = pr.pause;
863
+ const why = hold?.reason ? ` · ${oneLine(sanitizeText(hold.reason))}` : "";
864
+ lines.push(
865
+ truncate(
866
+ `${WARN}${STRONG}PAUSED${DRESET}${WARN} · ${leverHolderLabel(hold?.issuer, hold?.expires_at ?? undefined)}${why}${DRESET}`,
867
+ width,
868
+ ),
869
+ );
870
+ }
871
+ if (pr.review_failure) {
872
+ const command = pr.review_failure.command_id ? ` · ${pr.review_failure.command_id}` : "";
873
+ const round =
874
+ pr.review_failure.round_no !== null ? ` · round ${pr.review_failure.round_no}` : "";
875
+ lines.push(
876
+ truncate(
877
+ `${FG.anomaly}${STRONG}FAILED_LOUD${RESET}${FG.anomaly} · ${pr.review_failure.stage}${command}${round} · attempt ${pr.review_failure.attempts}/${pr.review_failure.retry_cap} · ${fmtAge(pr.review_failure.failed_at, now)} old${RESET}`,
878
+ width,
879
+ ),
880
+ );
881
+ const retry =
882
+ pr.review_failure.next_action === "retry_wait" && pr.review_failure.next_retry_at
883
+ ? `auto retry ${pr.review_failure.next_retry_at.slice(11, 16)}Z · epoch ${pr.review_failure.retry_epoch} · u now`
884
+ : "press u to recover";
885
+ lines.push(
886
+ truncate(`${FG.anomaly}${oneLine(pr.review_failure.detail)} · ${retry}${RESET}`, width),
887
+ );
888
+ }
889
+ lines.push(`${FG.dim}${"─".repeat(Math.max(0, width))}${RESET}`);
890
+ // status.json advances every poll and therefore owns current timing/plan;
891
+ // `why` is enriched and deliberately cached, so it is only the fallback for
892
+ // an older daemon that has not shipped current_round yet — and only while
893
+ // the snapshot says a round is under way: a cached `running` row outlives
894
+ // the round it describes until the next read lands.
895
+ const runningRound =
896
+ pr.current_round ??
897
+ (pr.state === "reviewing"
898
+ ? [...detail.rounds].reverse().find((r) => r.status === "running")
899
+ : undefined);
900
+ if (runningRound)
901
+ lines.push(
902
+ ...currentProgressLines(runningRound, progress, width, now, progressRows, lines.length),
903
+ );
904
+ const lastSummary = [...detail.rounds]
905
+ .reverse()
906
+ .find((r) => r.summary && r.summary.trim().length > 0)?.summary;
907
+ if (lastSummary) {
908
+ lines.push(truncate(section("NOW", "the session's own 3-4 sentence read"), width));
909
+ for (const l of wrap(sanitizeText(lastSummary).trim(), width)) lines.push(l);
910
+ }
911
+ if (detail.description !== undefined) {
912
+ lines.push(truncate(section("DESCRIPTION", "the PR body"), width));
913
+ const raw = sanitizeText(detail.description).trim();
914
+ const desc = raw.length > 0 ? wrap(raw, width) : ["(empty)"];
915
+ for (const l of desc.slice(0, 12)) lines.push(l);
916
+ if (desc.length > 12)
917
+ lines.push(`${FG.dim}… ${desc.length - 12} more lines (open on GitHub with g)${RESET}`);
918
+ }
919
+ lines.push(truncate(section("LEDGER", "findings the conversation tracks"), width));
920
+ if (detail.ledger) {
921
+ for (const l of sanitizeText(detail.ledger).split("\n")) {
922
+ const t = l.trim();
923
+ if (t.length > 0) for (const wl of wrap(t, width)) lines.push(wl);
924
+ }
925
+ } else {
926
+ lines.push(
927
+ truncate(`${FG.dim}none yet — the live ledger is pinned in the session${RESET}`, width),
928
+ );
929
+ }
930
+ // A cancelled round carries its own glyph, distinct from any other
931
+ // verdictless round, and the one loud cause — a round that stopped dead
932
+ // inside its deadline — is the only one that gets a color; a redeploy orphan
933
+ // stays dim on purpose.
934
+ const roundsBits = detail.rounds.map((r) => {
935
+ const cancelled = r.status === "cancelled";
936
+ const loud = cancelled && isLoudCancelCause(r.cancel_cause);
937
+ const v = r.verdict
938
+ ? (VERDICT_ABBREV[r.verdict] ?? r.verdict)
939
+ : cancelled
940
+ ? "✕"
941
+ : r.status === "running"
942
+ ? "…"
943
+ : "—";
944
+ const color = loud
945
+ ? FG.anomaly
946
+ : r.verdict === "APPROVE"
947
+ ? FG.approve
948
+ : r.verdict === "REQUEST_CHANGES"
949
+ ? FG.request_changes
950
+ : FG.dim;
951
+ return `r${r.round_no} ${color}${v}${RESET}`;
952
+ });
953
+ lines.push(
954
+ truncate(
955
+ `${section("ROUNDS", "review rounds + verdicts")} ${roundsBits.join(" · ") || "—"}`,
956
+ width,
957
+ ),
958
+ );
959
+ for (const r of detail.rounds) {
960
+ if (r.status !== "cancelled") continue;
961
+ const loud = isLoudCancelCause(r.cancel_cause);
962
+ lines.push(
963
+ truncate(
964
+ ` ${loud ? FG.anomaly : FG.dim}✕ r${r.round_no} cancelled — ${cancelCauseLabel(r.cancel_cause)}${RESET}`,
965
+ width,
966
+ ),
967
+ );
968
+ }
969
+ lines.push(...doubtedLines(detail.doubted, width));
970
+ // The profile's `[posting]` policy: survivors kept off the PR, same grammar.
971
+ lines.push(
972
+ ...doubtedLines(
973
+ detail.withheld,
974
+ width,
975
+ "WITHHELD",
976
+ "over the posting policy · kept, never posted",
977
+ ),
978
+ );
979
+ lines.push(
980
+ truncate(`${section("THREADS", "bot review threads")} ${detail.threads.total} known`, width),
981
+ );
982
+ if (detail.comments && detail.comments.length > 0) {
983
+ lines.push(truncate(section("COMMENTS", "latest PR conversation"), width));
984
+ for (const c of detail.comments) {
985
+ lines.push(
986
+ truncate(
987
+ `${FG.author}${c.author}${RESET} ${FG.dim}${fmtAge(c.created_at, now)} ago${RESET}`,
988
+ width,
989
+ ),
990
+ );
991
+ const body = sanitizeText(c.body)
992
+ .split("\n")
993
+ .map((t) => t.trim())
994
+ .filter((t) => t.length > 0)
995
+ .join(" ");
996
+ const bodyLines = wrap(body, Math.max(8, width - 2));
997
+ for (const bl of bodyLines.slice(0, 2)) lines.push(` ${bl}`);
998
+ if (bodyLines.length > 2) lines.push(` ${FG.dim}…${RESET}`);
999
+ }
1000
+ }
1001
+ return lines;
1002
+ }
1003
+
1004
+ /**
1005
+ * The `?` overlay. Pinned against docs/tui.md (tests/tui-surfaces.test.ts):
1006
+ * every key named here is in some surface's vocabulary, and every surface
1007
+ * the doc lists is named here. The bar shows what the current frame answers
1008
+ * to; this is the whole map.
1009
+ */
1010
+ export const HELP_LINES = [
1011
+ "1…8 the eight surfaces: board · memory · digest · tickets · deploy · metrics · feedback · scan",
1012
+ " the bar at the foot shows what THIS frame answers to; a button that is not there is a key that would do nothing",
1013
+ "r / s / ? / q refresh · reconnect VM ssh (the one ssh key-passphrase prompt) · this overlay · quit (asks first)",
1014
+ "Ctrl-c ×3 kill the TUI from anywhere (terminal restored) — the un-stuck hatch",
1015
+ " an alert in the top bar: click it or ⏎ to open the companion",
1016
+ "the companion column down the right edge is on every tab:",
1017
+ " type any key no tab claims (or /) — it is a terminal, every byte reaches pi; Ctrl-q releases",
1018
+ " wheel over the column (or PgUp/PgDn while focused) walks its scrollback · drag its left edge to resize",
1019
+ " the column is always there at full width — nothing in the TUI collapses it or goes full-screen",
1020
+ "[1] board — the verbs act on the SELECTED PR, so they exist only while one is selected, and only here:",
1021
+ "j/k ↓/↑ select PR (mouse: click a row; click again to focus its pi pane)",
1022
+ "⏎ / a focus the selected PR's pi pane (embedded, below the list; Ctrl-q back) — it is a live terminal, not a picture",
1023
+ "g open the PR on GitHub (PR numbers are Ctrl+clickable links)",
1024
+ "[ / ] a worker-hosted PR's pane is a transcript viewer, not a terminal: previous / next stream (main, then each lens); a click on a progress row picks its lens",
1025
+ "< / > past / later round in the viewer; PgUp / wheel parks it, End follows the tail again",
1026
+ " its input line (focused pane): ⏎ steers the running round, or sends a message to an idle PR; Alt-⏎ queues a follow-up",
1027
+ " Alt-p marks the next message may-post (shown, reset after send); Ctrl-c asks to abort the round (y confirms); [ ] < > are keys only while the line is empty",
1028
+ "m quick-message the selected PR's session",
1029
+ "u unwedge: cancel the running round and re-queue it (nothing posted, no GitHub write)",
1030
+ "e events overlay (j/k scroll, e/Esc close; click an event → its PR)",
1031
+ "F clear the board filter the PRS title is advertising",
1032
+ "I set up the repo: the companion runs its setup skill (survey, interview, the repo profile)",
1033
+ "[2] memory — the store, newest first, grouped by day:",
1034
+ "j/k · [ / ] select an entry · jump to the older / newer day",
1035
+ "e / p / x / n edit · pin or unpin · retire (x twice) · new #manual entry — painted beside the selected entry",
1036
+ "W / F open OpenViking Studio through the memory forward · clear the tag filter",
1037
+ "[3] digest — the EOD sweep's document: j/k scroll · [ / ] older / newer day",
1038
+ "[4] tickets — what adjudication filed on the tracker: j/k choose · [ / ] scroll the body · c actionable ⇄ all",
1039
+ "[5] deploy — V picks the version · D D deploys it (or click ▶ DEPLOY TO VM) · S S powers the VM app · x aborts a run / closes its log",
1040
+ "[6] metrics — w / m / a switch the 7d / 30d / all window · # <pr> ⏎ opens a PR timeline (Esc back)",
1041
+ "[7] feedback — the loop on one surface: signal → adjudication → proposal → change → revert",
1042
+ "t focus the next pane: inbox → proposed → changed",
1043
+ "a show / hide archived (acted-on) signals",
1044
+ "A / x accept / reject the focused rubric proposal (accept applies + auto-ratifies + commits locally)",
1045
+ "R then y arm and confirm a revert of the focused change — a local commit only, never pushed",
1046
+ "[8] scan — the whole-repo scan: units left, the unit's findings by validity tier (confirmed first; style grouped per rule) over the detail, the run on top:",
1047
+ "[ / ] · j/k previous / next unit · select a finding",
1048
+ "p / d promote (the companion files one ticket each; unconfirmed ones need an explicit yes) · dismiss with a reason (it becomes reviewer memory)",
1049
+ "f / F · t filter by state, lens, severity, tier, doubted · clear it · step the tier filter (confirmed → likely → doubtful → unknown)",
1050
+ "P the stage 2 plan: space toggles a row, ⏎ approves (hard stop = the kept total), Esc back",
1051
+ "x / D stop the running scan (the ledger resumes it) · ask the companion for a delta re-scan (it shows the price first)",
1052
+ "Ctrl-q release a focused pi pane or the companion column back to the board",
1053
+ "",
1054
+ "↑/↓ scroll when the terminal is short · any other key closes",
1055
+ ];
1056
+
1057
+ // ---- live pi pane ----------------------------------------------------------
1058
+
1059
+ /** The viewer header after its name: where it is in the PR's logs, whether it
1060
+ * follows the tail, and the keys that move it. */
1061
+ function viewerHeaderTail(
1062
+ pane: PiPaneView,
1063
+ viewer: NonNullable<PiPaneView["viewer"]>,
1064
+ focused: boolean,
1065
+ ): string {
1066
+ if (viewer.round === undefined) return `${FG.dim} · no round log yet${RESET}`;
1067
+ const round = `r${viewer.round}${viewer.rounds > 1 ? ` (${viewer.roundIndex}/${viewer.rounds})` : ""}${viewer.atLatest ? "" : " · past"}`;
1068
+ const stream = `${viewer.stream}${viewer.streams > 1 ? ` (${viewer.streamIndex}/${viewer.streams})` : ""}`;
1069
+ const state = pane.stale
1070
+ ? "stale — refresh failing"
1071
+ : viewer.live === true
1072
+ ? "running"
1073
+ : viewer.live === false
1074
+ ? "settled"
1075
+ : "";
1076
+ const follow =
1077
+ pane.offset > 0
1078
+ ? `${FG.accent} · ↑ ${pane.offset} rows back${RESET}${FG.dim} · End follows${RESET}`
1079
+ : `${FG.dim} · following${RESET}`;
1080
+ const keys = " · [ ] stream · < > round";
1081
+ const mode = focused ? " · Ctrl-q back to board" : "";
1082
+ return `${FG.dim} · ${round} · ${stream}${state ? ` · ${state}` : ""}${RESET}${follow}${FG.dim}${keys}${mode}${RESET}`;
1083
+ }
1084
+
1085
+ /**
1086
+ * Header of the pane: which session, whether it is live, whether it currently
1087
+ * owns the keyboard, and how far back in scrollback it is parked.
1088
+ */
1089
+ export function piHeader(
1090
+ pr: number | undefined,
1091
+ pane: PiPaneView | undefined,
1092
+ focused = false,
1093
+ ): string {
1094
+ if (pr === undefined) return `${FG.dim}PI — no PR selected${RESET}`;
1095
+ const mine = pane && pane.pr === pr ? pane : undefined;
1096
+ // A pi terminal (a debug round's or a takeover's) names its tmux session;
1097
+ // everything else is the PR's live transcript — there is no `pr-<n>` tmux
1098
+ // session to name.
1099
+ const session = mine?.viewer ? undefined : mine?.session;
1100
+ const viewer = mine?.viewer;
1101
+ const name =
1102
+ session === undefined
1103
+ ? focused
1104
+ ? `${ACCENT_BG}LIVE pr-${pr}${DRESET}`
1105
+ : `${MUTED}LIVE ${DRESET}${ACCENT}pr-${pr}${DRESET}`
1106
+ : focused
1107
+ ? `${ACCENT_BG}PI ${session}${DRESET}`
1108
+ : `${MUTED}PI ${DRESET}${ACCENT}${session}${DRESET}`;
1109
+ if (!mine) return `${name}${FG.dim} · connecting…${RESET}`;
1110
+ if (viewer) return `${name}${viewerHeaderTail(mine, viewer, focused)}`;
1111
+ if (mine.missing) {
1112
+ return `${name}${FG.dim} · no pi session for #${pr}${mine.stale ? " · (stale)" : ""}${RESET}`;
1113
+ }
1114
+ const scroll =
1115
+ mine.offset > 0
1116
+ ? `${FG.accent} · ↑ ${mine.offset} lines back${RESET}${FG.dim} · End live${RESET}`
1117
+ : "";
1118
+ const mode = focused
1119
+ ? `${FG.accent} · focused${RESET}${FG.dim} · Ctrl-q back to board${RESET}`
1120
+ : `${FG.dim} · live mirror · click to type${RESET}`;
1121
+ return `${name}${mode}${scroll}`;
1122
+ }
1123
+
1124
+ /** The window of the capture that fits the region, already width-clamped.
1125
+ * `pane.offset` parks the window that many lines above the live tail. */
1126
+ export function piBody(
1127
+ pr: number | undefined,
1128
+ pane: PiPaneView | undefined,
1129
+ width: number,
1130
+ height: number,
1131
+ ): string[] {
1132
+ if (pr === undefined || height <= 0) return [];
1133
+ const mine = pane && pane.pr === pr ? pane : undefined;
1134
+ if (!mine) return [`${FG.dim}waiting for the first capture…${RESET}`];
1135
+ if (mine.lines.length === 0) return mine.missing ? [] : [`${FG.dim}(pane empty)${RESET}`];
1136
+ // Bottom-anchored, like the terminal it mirrors: new output lands on a
1137
+ // stable edge instead of shifting the whole pane down. A scrollback offset
1138
+ // moves that edge up through the captured history.
1139
+ const src =
1140
+ mine.offset > 0
1141
+ ? mine.lines.slice(0, Math.max(0, mine.lines.length - mine.offset))
1142
+ : mine.lines;
1143
+ const tail = src.slice(-height).map((l) => truncate(l, width));
1144
+ return [...new Array<string>(Math.max(0, height - tail.length)).fill(""), ...tail];
1145
+ }
1146
+
1147
+ // ---- events overlay ---------------------------------------------------------
1148
+
1149
+ /** The demoted EVENTS strip: a floating, scrollable band over the board. */
1150
+ function renderEventsOverlay(
1151
+ frame: Frame,
1152
+ view: BoardView,
1153
+ cols: number,
1154
+ rows: number,
1155
+ barRows: number,
1156
+ ): void {
1157
+ const { top, height } = eventsOverlayBand(rows, barRows);
1158
+ const visible = Math.max(0, height - 2);
1159
+ // The band is opaque: whatever it paints over must not stay clickable
1160
+ // underneath it (a hidden PR row, the pi pane's focus zone). Zones are
1161
+ // clipped to the rows the band leaves visible; only event rows below are
1162
+ // declared in its place.
1163
+ const bandY1 = top + 1; // frame row → terminal y
1164
+ const bandY2 = top + height;
1165
+ frame.zones = frame.zones.flatMap((z) => {
1166
+ if (z.y2 < bandY1 || z.y1 > bandY2) return [z];
1167
+ const kept: typeof frame.zones = [];
1168
+ if (z.y1 < bandY1) kept.push({ ...z, y2: bandY1 - 1 });
1169
+ if (z.y2 > bandY2) kept.push({ ...z, y1: bandY2 + 1 });
1170
+ return kept;
1171
+ });
1172
+ frame.set(
1173
+ top,
1174
+ rule(cols, "├", "┤", [
1175
+ sectionTitle("EVENTS"),
1176
+ `${MUTED}what the daemon just did${DRESET}`,
1177
+ `${MUTED}j/k scroll · e/Esc close${DRESET}`,
1178
+ ]),
1179
+ );
1180
+ const slice = view.events.slice(view.eventsScroll, view.eventsScroll + visible);
1181
+ for (let i = 0; i < visible; i++) {
1182
+ const e = slice[i];
1183
+ const text = e
1184
+ ? e.anomaly
1185
+ ? `${FG.anomaly}${truncate(oneLine(e.text), cols - 4)}${RESET}`
1186
+ : truncate(oneLine(e.text), cols - 4)
1187
+ : "";
1188
+ frame.boxRow(top + 1 + i, [{ text: ` ${text}`, width: cols - 2 }]);
1189
+ const prMatch = e ? /#(\d+)/.exec(e.text) : null;
1190
+ if (prMatch) {
1191
+ frame.zones.push({
1192
+ x1: 2,
1193
+ y1: top + 1 + i + 1,
1194
+ x2: cols - 1,
1195
+ y2: top + 1 + i + 1,
1196
+ action: { t: "event-jump", pr: Number(prMatch[1]) } satisfies BoardAction,
1197
+ });
1198
+ }
1199
+ }
1200
+ frame.set(top + height - 1, rule(cols, "├", "┤", []));
1201
+ }
1202
+
1203
+ // ---- render ----------------------------------------------------------------
1204
+
1205
+ /**
1206
+ * The draft as one paintable line of at most `width` cells: control bytes
1207
+ * stripped, each newline shown as `⏎`, and — when it overflows — its END kept
1208
+ * behind a `…`, because the end is where the operator types (the `▁` cursor).
1209
+ */
1210
+ function draftLine(text: string, width: number): string {
1211
+ const full = `${sanitizeText(text).replace(/\n/g, "⏎")}▁`;
1212
+ if (displayWidth(full) <= width) return full;
1213
+ const chars = Array.from(full);
1214
+ let tail = "";
1215
+ let w = 0;
1216
+ for (let i = chars.length - 1; i >= 0; i--) {
1217
+ const cw = charWidth(chars[i]!.codePointAt(0) ?? 0);
1218
+ if (w + cw > width - 1) break;
1219
+ tail = chars[i] + tail;
1220
+ w += cw;
1221
+ }
1222
+ return `…${tail}`;
1223
+ }
1224
+
1225
+ /** The input box's three rows: its target, the text being typed, its keys. */
1226
+ function inputBoxLines(box: NonNullable<BoardView["inputBox"]>, width: number): string[] {
1227
+ return [
1228
+ ` ${HEADING}MSG → ${oneLine(box.target)}${DRESET}`,
1229
+ ` > ${draftLine(box.text, Math.max(1, width - 3))}`,
1230
+ ` ⏎ send · Esc cancel · Alt-⏎ newline`,
1231
+ ];
1232
+ }
1233
+
1234
+ /**
1235
+ * The input box over the foot of a tab's own screen — the board paints it in
1236
+ * its pi region, but a prompt a tab opens (a memory edit, a rejection reason)
1237
+ * types into the same box and must be seen on that tab: a divider and the
1238
+ * box's three rows, just above the frame's bottom border.
1239
+ */
1240
+ export function overlayInputBox(frame: Frame, box: NonNullable<BoardView["inputBox"]>): void {
1241
+ const top = frame.rows - 5;
1242
+ if (top < 1) return;
1243
+ frame.meta["inputBoxTop"] = top;
1244
+ frame.meta["inputBoxBottom"] = top + 3;
1245
+ frame.meta["inputBoxCols"] = frame.cols;
1246
+ frame.set(top, `├${"─".repeat(Math.max(0, frame.cols - 2))}┤`);
1247
+ inputBoxLines(box, frame.cols - 2).forEach((text, i) =>
1248
+ frame.boxRow(top + 1 + i, [{ text, width: frame.cols - 2 }]),
1249
+ );
1250
+ }
1251
+
1252
+ export function renderBoard(view: BoardView, cols: number, rows: number): Frame {
1253
+ const frame = new Frame(cols, rows);
1254
+ const now = view.now ?? new Date();
1255
+ frame.set(0, topBar(view, cols));
1256
+ const az = alertZone(view, cols);
1257
+ if (az)
1258
+ frame.zones.push({
1259
+ x1: az.x1,
1260
+ y1: 1,
1261
+ x2: az.x2,
1262
+ y2: 1,
1263
+ action: { t: "companion" } satisfies BoardAction,
1264
+ });
1265
+ const live = boardKeys(view);
1266
+ const barRows = barHeight("board", cols, live.bar);
1267
+ const pRows = piRows(rows, barRows);
1268
+ const bodyRows = rows - 2 - barRows - pRows; // top bar + command bar + bottom border
1269
+ const sorted = visiblePrs(view);
1270
+ const listW = listWidth(sorted, cols, now);
1271
+ const detailW = cols - 2 - listW - 1;
1272
+ const selectedPr = sorted[view.selected];
1273
+ frame.meta = { listW, bodyRows, piTop: 1 + bodyRows, piRows: pRows };
1274
+
1275
+ if (view.help) {
1276
+ frame.boxRow(1, [
1277
+ { text: ` ${sectionTitle("KEYS", "everything the TUI answers to")}`, width: cols - 2 },
1278
+ ]);
1279
+ const helpRows = Math.max(0, rows - 3);
1280
+ const helpMax = Math.max(0, HELP_LINES.length - helpRows);
1281
+ const helpTop = Math.min(Math.max(0, view.helpScroll ?? 0), helpMax);
1282
+ frame.meta["helpMax"] = helpMax;
1283
+ HELP_LINES.slice(helpTop, helpTop + helpRows).forEach((l, i) => {
1284
+ frame.boxRow(2 + i, [{ text: ` ${l}`, width: cols - 2 }]);
1285
+ });
1286
+ for (let r = 2 + Math.min(HELP_LINES.length - helpTop, helpRows); r < rows - 1; r++)
1287
+ frame.boxRow(r, [{ text: "", width: cols - 2 }]);
1288
+ frame.set(rows - 1, rule(cols, "└", "┘", []));
1289
+ return frame;
1290
+ }
1291
+
1292
+ // Board body: list | detail.
1293
+ //
1294
+ // A snapshot that never arrived is unknown, not empty. The count
1295
+ // reads `?`, the rows are replaced by the reason and the remedy, and the
1296
+ // detail pane says "no data" rather than "no PR selected".
1297
+ const known = view.snapshot !== undefined;
1298
+ const flabel = filterLabel(view.boardFilter);
1299
+ const listLines: string[] = known
1300
+ ? [section(`PRS (${sorted.length})`, flabel !== "" ? flabel : "the daemon's live set")]
1301
+ : [
1302
+ section("PRS (?)", "unknown — the daemon has not answered"),
1303
+ ...unknownSourceLines(view.connection, listW - 1),
1304
+ ];
1305
+ // The list scrolls with the selection: the verbs act on the selected PR, so
1306
+ // it must never sit off-screen (`windowStart`, the tabs' shared rule). The
1307
+ // header stays put; the last body row is the legend, painted over whatever
1308
+ // would sit there, so the PR slots are the rows between the two.
1309
+ const prSlots = Math.max(1, bodyRows - 1 - listLines.length);
1310
+ const start = windowStart(view.selected, sorted.length, prSlots);
1311
+ sorted.slice(start, start + prSlots).forEach((pr, offset) => {
1312
+ const i = start + offset;
1313
+ listLines.push(styledRow(pr, i === view.selected, listW - 1, now, view.repo));
1314
+ const y = 1 + listLines.length - 1 + 1; // frame row of this PR line (below top bar)
1315
+ // Only painted rows get zones: with a long list, off-screen rows would
1316
+ // otherwise leave phantom click targets over the pi region and the
1317
+ // bar-row gaps — a click there could select (or double-click into the
1318
+ // pi pane of) an invisible PR. The slice above holds only painted rows,
1319
+ // but a body too short for even one slot must still declare nothing.
1320
+ if (listLines.length > bodyRows - 1) return;
1321
+ frame.zones.push({
1322
+ x1: 2,
1323
+ y1: y,
1324
+ x2: listW,
1325
+ y2: y,
1326
+ action: { t: "select-pr", pr: pr.number } satisfies BoardAction,
1327
+ });
1328
+ frame.zones.push({
1329
+ x1: 4,
1330
+ y1: y,
1331
+ x2: 4 + String(pr.number).length + 1,
1332
+ y2: y,
1333
+ action: { t: "open-github", pr: pr.number } satisfies BoardAction,
1334
+ });
1335
+ });
1336
+ const progressRows: ProgressRow[] = [];
1337
+ const dLines = known
1338
+ ? detailLines(
1339
+ view.detail,
1340
+ selectedPr,
1341
+ detailW - 2,
1342
+ now,
1343
+ view.progress?.pr === selectedPr?.number ? view.progress : undefined,
1344
+ progressRows,
1345
+ )
1346
+ : unknownDetailLines(view.connection, detailW - 2);
1347
+ // With the transcript viewer in the pane, a lens row of the progress panel is
1348
+ // a click target: it selects that lens's transcript. A painted row only (an
1349
+ // off-screen one would be a phantom target), and only while the viewer is up.
1350
+ if (view.pi?.viewer && view.pi.pr === selectedPr?.number) {
1351
+ for (const row of progressRows) {
1352
+ if (row.line >= bodyRows) continue;
1353
+ frame.zones.push({
1354
+ x1: listW + 3,
1355
+ y1: row.line + 2,
1356
+ x2: cols - 1,
1357
+ y2: row.line + 2,
1358
+ action: { t: "viewer-lens", lens: row.lens, nth: row.nth } satisfies BoardAction,
1359
+ });
1360
+ }
1361
+ }
1362
+ for (let r = 0; r < bodyRows; r++) {
1363
+ const isLegendRow = r === bodyRows - 1;
1364
+ frame.boxRow(1 + r, [
1365
+ { text: isLegendRow ? ` ${legendLine()}` : ` ${listLines[r] ?? ""}`, width: listW },
1366
+ { text: ` ${dLines[r] ?? ""}`, width: detailW },
1367
+ ]);
1368
+ }
1369
+
1370
+ // Live pi mirror (or the quick-message input box) — the lower region.
1371
+ const piTop = 1 + bodyRows;
1372
+ const piLines = Math.max(0, pRows - 2); // the region is divider + header + lines
1373
+ const panePr = view.piPr ?? selectedPr?.number;
1374
+ const piFocused = view.piFocus === true && panePr !== undefined;
1375
+ if (pRows > 0) {
1376
+ // Focused, the divider is drawn heavy and accented — the pane visibly
1377
+ // becomes its own window rather than a strip of the board.
1378
+ const bar = piFocused ? "━" : "─";
1379
+ const inner = `${fit(bar.repeat(listW), listW)}${piFocused ? bar : "┴"}${bar.repeat(detailW)}`;
1380
+ frame.set(piTop, `│${piFocused ? `${FG.accent}${inner}${RESET}` : inner}│`);
1381
+ }
1382
+ if (view.inputBox) {
1383
+ inputBoxLines(view.inputBox, cols - 2).forEach((text, i) =>
1384
+ frame.boxRow(piTop + 1 + i, [{ text, width: cols - 2 }]),
1385
+ );
1386
+ for (let i = 4; i < pRows; i++) frame.boxRow(piTop + i, [{ text: "", width: cols - 2 }]);
1387
+ } else if (pRows > 0) {
1388
+ frame.boxRow(piTop + 1, [
1389
+ {
1390
+ text: ` ${piHeader(panePr, view.pi, piFocused)}${view.noticeLine ? ` ${view.noticeLine}` : ""}`,
1391
+ width: cols - 2,
1392
+ },
1393
+ ]);
1394
+ // Width comes from piPaneSize so the rectangle tmux is resized to and the
1395
+ // rectangle the capture is painted into cannot drift apart.
1396
+ const paneCols = piPaneSize(cols, rows, barRows)?.cols ?? 0;
1397
+ // The viewer keeps its last row for its input line (the PR-pane rule): the
1398
+ // transcript above it, so a mirror's rows are untouched.
1399
+ const line =
1400
+ piLines > 1 && view.pi?.viewer && view.pi.pr === panePr && view.viewerInput?.pr === panePr
1401
+ ? view.viewerInput
1402
+ : undefined;
1403
+ const bodyLines = line ? piLines - 1 : piLines;
1404
+ const body = piBody(panePr, view.pi, paneCols, bodyLines);
1405
+ for (let i = 0; i < bodyLines; i++)
1406
+ frame.boxRow(piTop + 2 + i, [{ text: ` ${body[i] ?? ""}`, width: cols - 2 }]);
1407
+ if (line)
1408
+ frame.boxRow(piTop + 2 + bodyLines, [
1409
+ { text: ` ${viewerInputRow(line, paneCols)}`, width: cols - 2 },
1410
+ ]);
1411
+ // Clicking anywhere in the region (divider, header or body) gives the pane
1412
+ // the keyboard, in place — nothing on the board takes the whole terminal.
1413
+ if (panePr !== undefined) {
1414
+ frame.zones.push({
1415
+ x1: 2,
1416
+ y1: piTop + 1,
1417
+ x2: cols - 1,
1418
+ y2: piTop + pRows,
1419
+ action: { t: "pi-focus" } satisfies BoardAction,
1420
+ });
1421
+ }
1422
+ }
1423
+
1424
+ // EVENTS: a floating overlay on `e`.
1425
+ // The quick-message editor owns the keyboard while open, so it stays visible.
1426
+ if (view.eventsFocus && !view.inputBox) renderEventsOverlay(frame, view, cols, rows, barRows);
1427
+
1428
+ // The shared command bar: exactly the verbs this frame answers to.
1429
+ paintCmdBar(frame, "board", cols, rows, live.bar);
1430
+ frame.set(rows - 1, rule(cols, "└", "┘", []));
1431
+ return frame;
1432
+ }