@quickchatai/quickreview 1.1.0 → 1.3.1

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 (471) hide show
  1. package/README.md +137 -57
  2. package/bin/qcr.js +1 -1
  3. package/config.example.toml +60 -14
  4. package/deploy/ansible/README.md +20 -12
  5. package/deploy/ansible/inventory.example.ini +1 -1
  6. package/deploy/collect-secrets.sh +1 -1
  7. package/deploy/qcr-update.sh +1 -1
  8. package/dist/extensions/cli/home.js +14 -0
  9. package/dist/extensions/cli/home.js.map +1 -0
  10. package/dist/extensions/cli/login-route.js +5 -0
  11. package/dist/extensions/cli/login-route.js.map +1 -0
  12. package/dist/extensions/cli/selfimprove.js +18 -0
  13. package/dist/extensions/cli/selfimprove.js.map +1 -0
  14. package/dist/extensions/home.js +350 -0
  15. package/dist/extensions/home.js.map +1 -0
  16. package/dist/extensions/login-route.js +180 -0
  17. package/dist/extensions/login-route.js.map +1 -0
  18. package/dist/extensions/onboarding.js +35 -11
  19. package/dist/extensions/onboarding.js.map +1 -1
  20. package/dist/extensions/selfimprove.js +49 -0
  21. package/dist/extensions/selfimprove.js.map +1 -0
  22. package/dist/extensions/setup-scan.js +23 -592
  23. package/dist/extensions/setup-scan.js.map +1 -1
  24. package/dist/extensions/setup.js +2 -47
  25. package/dist/extensions/setup.js.map +1 -1
  26. package/dist/src/archive/restore.js +18 -15
  27. package/dist/src/archive/restore.js.map +1 -1
  28. package/dist/src/botworker.js +13 -2
  29. package/dist/src/botworker.js.map +1 -1
  30. package/dist/src/channelfs.js +3 -26
  31. package/dist/src/channelfs.js.map +1 -1
  32. package/dist/src/ciretry.js +2 -1
  33. package/dist/src/ciretry.js.map +1 -1
  34. package/dist/src/cli.js +54 -61
  35. package/dist/src/cli.js.map +1 -1
  36. package/dist/src/cliops.js +3 -2
  37. package/dist/src/cliops.js.map +1 -1
  38. package/dist/src/commands.js +25 -2
  39. package/dist/src/commands.js.map +1 -1
  40. package/dist/src/config.js +32 -2
  41. package/dist/src/config.js.map +1 -1
  42. package/dist/src/daemon.js +50 -8
  43. package/dist/src/daemon.js.map +1 -1
  44. package/dist/src/debuground.js +1 -0
  45. package/dist/src/debuground.js.map +1 -1
  46. package/dist/src/debugroundpi.js +6 -1
  47. package/dist/src/debugroundpi.js.map +1 -1
  48. package/dist/src/dispatch.js +5 -4
  49. package/dist/src/dispatch.js.map +1 -1
  50. package/dist/src/engine/caserunner-sdk.js +15 -251
  51. package/dist/src/engine/caserunner-sdk.js.map +1 -1
  52. package/dist/src/engine/debugpi.js +17 -5
  53. package/dist/src/engine/debugpi.js.map +1 -1
  54. package/dist/src/execboundary.js +10 -3
  55. package/dist/src/execboundary.js.map +1 -1
  56. package/dist/src/github.js +51 -16
  57. package/dist/src/github.js.map +1 -1
  58. package/dist/src/githubtoken.js +346 -0
  59. package/dist/src/githubtoken.js.map +1 -0
  60. package/dist/src/home/adapters.js +349 -0
  61. package/dist/src/home/adapters.js.map +1 -0
  62. package/dist/src/home/advanced.js +318 -0
  63. package/dist/src/home/advanced.js.map +1 -0
  64. package/dist/src/home/advancedtools.js +57 -0
  65. package/dist/src/home/advancedtools.js.map +1 -0
  66. package/dist/src/home/autoreview.js +325 -0
  67. package/dist/src/home/autoreview.js.map +1 -0
  68. package/dist/src/home/daemonstatus.js +32 -0
  69. package/dist/src/home/daemonstatus.js.map +1 -0
  70. package/dist/src/home/devicelogin.js +61 -0
  71. package/dist/src/home/devicelogin.js.map +1 -0
  72. package/dist/src/home/launch.js +202 -0
  73. package/dist/src/home/launch.js.map +1 -0
  74. package/dist/src/home/loginowner.js +205 -0
  75. package/dist/src/home/loginowner.js.map +1 -0
  76. package/dist/src/home/memory.js +122 -0
  77. package/dist/src/home/memory.js.map +1 -0
  78. package/dist/src/home/model.js +811 -0
  79. package/dist/src/home/model.js.map +1 -0
  80. package/dist/src/home/panel.js +520 -0
  81. package/dist/src/home/panel.js.map +1 -0
  82. package/dist/src/home/ports.js +19 -0
  83. package/dist/src/home/ports.js.map +1 -0
  84. package/dist/src/home/review.js +163 -0
  85. package/dist/src/home/review.js.map +1 -0
  86. package/dist/src/home/reviews.js +478 -0
  87. package/dist/src/home/reviews.js.map +1 -0
  88. package/dist/src/home/setup.js +507 -0
  89. package/dist/src/home/setup.js.map +1 -0
  90. package/dist/src/home/setupadapters.js +214 -0
  91. package/dist/src/home/setupadapters.js.map +1 -0
  92. package/dist/src/home/setupcard.js +199 -0
  93. package/dist/src/home/setupcard.js.map +1 -0
  94. package/dist/src/home/setuptools.js +45 -0
  95. package/dist/src/home/setuptools.js.map +1 -0
  96. package/dist/src/home/spend.js +160 -0
  97. package/dist/src/home/spend.js.map +1 -0
  98. package/dist/src/home/switches.js +209 -0
  99. package/dist/src/home/switches.js.map +1 -0
  100. package/dist/src/home/switchon.js +439 -0
  101. package/dist/src/home/switchon.js.map +1 -0
  102. package/dist/src/home/toolnames.js +41 -0
  103. package/dist/src/home/toolnames.js.map +1 -0
  104. package/dist/src/home/tune.js +341 -0
  105. package/dist/src/home/tune.js.map +1 -0
  106. package/dist/src/home/words.js +7 -0
  107. package/dist/src/home/words.js.map +1 -0
  108. package/dist/src/homes.js +14 -0
  109. package/dist/src/homes.js.map +1 -1
  110. package/dist/src/install.js +1 -1
  111. package/dist/src/install.js.map +1 -1
  112. package/dist/src/ipc/workerspec.js +8 -0
  113. package/dist/src/ipc/workerspec.js.map +1 -1
  114. package/dist/src/live/publish.js +1 -1
  115. package/dist/src/live/publish.js.map +1 -1
  116. package/dist/src/livecli.js +1 -1
  117. package/dist/src/livecli.js.map +1 -1
  118. package/dist/src/loginhint.js +8 -3
  119. package/dist/src/loginhint.js.map +1 -1
  120. package/dist/src/loginrefresh/handover.js +121 -0
  121. package/dist/src/loginrefresh/handover.js.map +1 -0
  122. package/dist/src/loginrefresh/lock.js +54 -3
  123. package/dist/src/loginrefresh/lock.js.map +1 -1
  124. package/dist/src/loginrefresh/pirefresh.js +3 -0
  125. package/dist/src/loginrefresh/pirefresh.js.map +1 -1
  126. package/dist/src/loginrefresh/refresher.js +33 -5
  127. package/dist/src/loginrefresh/refresher.js.map +1 -1
  128. package/dist/src/loginrefresh/supervisor.js +18 -1
  129. package/dist/src/loginrefresh/supervisor.js.map +1 -1
  130. package/dist/src/npmverbs.js +46 -14
  131. package/dist/src/npmverbs.js.map +1 -1
  132. package/dist/src/onboard/choices.js +35 -4
  133. package/dist/src/onboard/choices.js.map +1 -1
  134. package/dist/src/onboard/engine.js +32 -306
  135. package/dist/src/onboard/engine.js.map +1 -1
  136. package/dist/src/onboard/pipin.js +22 -0
  137. package/dist/src/onboard/pipin.js.map +1 -0
  138. package/dist/src/onboard/stepforward.js +41 -0
  139. package/dist/src/onboard/stepforward.js.map +1 -0
  140. package/dist/src/onboard/steps/appslug.js +4 -1
  141. package/dist/src/onboard/steps/appslug.js.map +1 -1
  142. package/dist/src/onboard/steps/bringup.js +3 -2
  143. package/dist/src/onboard/steps/bringup.js.map +1 -1
  144. package/dist/src/onboard/steps/compose.js +29 -0
  145. package/dist/src/onboard/steps/compose.js.map +1 -1
  146. package/dist/src/onboard/steps/configfill.js +120 -39
  147. package/dist/src/onboard/steps/configfill.js.map +1 -1
  148. package/dist/src/onboard/steps/githubaccount.js +595 -0
  149. package/dist/src/onboard/steps/githubaccount.js.map +1 -0
  150. package/dist/src/onboard/steps/githubapp.js +65 -7
  151. package/dist/src/onboard/steps/githubapp.js.map +1 -1
  152. package/dist/src/onboard/steps/golive.js +2 -0
  153. package/dist/src/onboard/steps/golive.js.map +1 -1
  154. package/dist/src/onboard/steps/index.js +11 -0
  155. package/dist/src/onboard/steps/index.js.map +1 -1
  156. package/dist/src/onboard/steps/ingress.js +80 -25
  157. package/dist/src/onboard/steps/ingress.js.map +1 -1
  158. package/dist/src/onboard/steps/llmauth.js +103 -30
  159. package/dist/src/onboard/steps/llmauth.js.map +1 -1
  160. package/dist/src/onboard/steps/loginshare.js +213 -0
  161. package/dist/src/onboard/steps/loginshare.js.map +1 -0
  162. package/dist/src/onboard/steps/memory.js +67 -3
  163. package/dist/src/onboard/steps/memory.js.map +1 -1
  164. package/dist/src/onboard/steps/proof.js +366 -56
  165. package/dist/src/onboard/steps/proof.js.map +1 -1
  166. package/dist/src/onboard/steps/repoconfig.js +72 -0
  167. package/dist/src/onboard/steps/repoconfig.js.map +1 -0
  168. package/dist/src/onboard/steps/topology.js +6 -6
  169. package/dist/src/onboard/steps/topology.js.map +1 -1
  170. package/dist/src/onboard/steps/transport.js +9 -1
  171. package/dist/src/onboard/steps/transport.js.map +1 -1
  172. package/dist/src/onboard/steps/tuiroute.js +102 -0
  173. package/dist/src/onboard/steps/tuiroute.js.map +1 -0
  174. package/dist/src/onboard/steps/verifyup.js +17 -1
  175. package/dist/src/onboard/steps/verifyup.js.map +1 -1
  176. package/dist/src/onboard/toolnames.js +30 -0
  177. package/dist/src/onboard/toolnames.js.map +1 -0
  178. package/dist/src/operatorwrite.js +23 -0
  179. package/dist/src/operatorwrite.js.map +1 -0
  180. package/dist/src/outcomes.js +4 -1
  181. package/dist/src/outcomes.js.map +1 -1
  182. package/dist/src/piagentdir.js +13 -0
  183. package/dist/src/piagentdir.js.map +1 -0
  184. package/dist/src/pilogin.js +31 -16
  185. package/dist/src/pilogin.js.map +1 -1
  186. package/dist/src/posting-journal.js +2 -1
  187. package/dist/src/posting-journal.js.map +1 -1
  188. package/dist/src/posting-outbox.js +6 -2
  189. package/dist/src/posting-outbox.js.map +1 -1
  190. package/dist/src/posting-receipt.js +14 -1
  191. package/dist/src/posting-receipt.js.map +1 -1
  192. package/dist/src/qcrbin.js +16 -11
  193. package/dist/src/qcrbin.js.map +1 -1
  194. package/dist/src/recheck.js +2 -1
  195. package/dist/src/recheck.js.map +1 -1
  196. package/dist/src/reconcile.js +22 -2
  197. package/dist/src/reconcile.js.map +1 -1
  198. package/dist/src/reviewargs.js +122 -0
  199. package/dist/src/reviewargs.js.map +1 -0
  200. package/dist/src/reviewcli.js +7 -161
  201. package/dist/src/reviewcli.js.map +1 -1
  202. package/dist/src/reviewdetach.js +76 -11
  203. package/dist/src/reviewdetach.js.map +1 -1
  204. package/dist/src/reviewhistory.js +32 -6
  205. package/dist/src/reviewhistory.js.map +1 -1
  206. package/dist/src/reviewnow.js +45 -0
  207. package/dist/src/reviewnow.js.map +1 -0
  208. package/dist/src/scancli.js +39 -691
  209. package/dist/src/scancli.js.map +1 -1
  210. package/dist/src/selfimprove.js +66 -0
  211. package/dist/src/selfimprove.js.map +1 -0
  212. package/dist/src/setup/adapters.js +5 -223
  213. package/dist/src/setup/adapters.js.map +1 -1
  214. package/dist/src/setup/caserunner.js +15 -249
  215. package/dist/src/setup/caserunner.js.map +1 -1
  216. package/dist/src/setup/engine.js +32 -341
  217. package/dist/src/setup/engine.js.map +1 -1
  218. package/dist/src/setup/facts.js +3 -592
  219. package/dist/src/setup/facts.js.map +1 -1
  220. package/dist/src/setup/findings.js +3 -947
  221. package/dist/src/setup/findings.js.map +1 -1
  222. package/dist/src/setup/presentation.js +3 -8
  223. package/dist/src/setup/presentation.js.map +1 -1
  224. package/dist/src/setup/scan.js +3 -724
  225. package/dist/src/setup/scan.js.map +1 -1
  226. package/dist/src/setup/scanbuild.js +9 -92
  227. package/dist/src/setup/scanbuild.js.map +1 -1
  228. package/dist/src/setup/scanlease.js +3 -250
  229. package/dist/src/setup/scanlease.js.map +1 -1
  230. package/dist/src/setup/scanreport.js +3 -288
  231. package/dist/src/setup/scanreport.js.map +1 -1
  232. package/dist/src/setup/scanrun.js +3 -1732
  233. package/dist/src/setup/scanrun.js.map +1 -1
  234. package/dist/src/setup/scantree.js +3 -256
  235. package/dist/src/setup/scantree.js.map +1 -1
  236. package/dist/src/setup/stage2request.js +3 -258
  237. package/dist/src/setup/stage2request.js.map +1 -1
  238. package/dist/src/setup/unitcut.js +3 -436
  239. package/dist/src/setup/unitcut.js.map +1 -1
  240. package/dist/src/state.js +2 -0
  241. package/dist/src/state.js.map +1 -1
  242. package/dist/src/statemachine.js +4 -1
  243. package/dist/src/statemachine.js.map +1 -1
  244. package/dist/src/takeover.js +1 -0
  245. package/dist/src/takeover.js.map +1 -1
  246. package/dist/src/takeoverpi.js +15 -1
  247. package/dist/src/takeoverpi.js.map +1 -1
  248. package/dist/src/tui/app.js +49 -0
  249. package/dist/src/tui/app.js.map +1 -1
  250. package/dist/src/tui/board.js +88 -12
  251. package/dist/src/tui/board.js.map +1 -1
  252. package/dist/src/tui/cmdbar.js +4 -1
  253. package/dist/src/tui/cmdbar.js.map +1 -1
  254. package/dist/src/tui/companion.js +76 -11
  255. package/dist/src/tui/companion.js.map +1 -1
  256. package/dist/src/tui/connect.js +7 -1
  257. package/dist/src/tui/connect.js.map +1 -1
  258. package/dist/src/tui/deploytab.js +2 -2
  259. package/dist/src/tui/deploytab.js.map +1 -1
  260. package/dist/src/tui/filters.js +5 -2
  261. package/dist/src/tui/filters.js.map +1 -1
  262. package/dist/src/tui/ghread.js +61 -5
  263. package/dist/src/tui/ghread.js.map +1 -1
  264. package/dist/src/tui/index.js +109 -20
  265. package/dist/src/tui/index.js.map +1 -1
  266. package/dist/src/tui/memforward.js +39 -2
  267. package/dist/src/tui/memforward.js.map +1 -1
  268. package/dist/src/tui/memory.js +3 -16
  269. package/dist/src/tui/memory.js.map +1 -1
  270. package/dist/src/tui/memoryurl.js +22 -0
  271. package/dist/src/tui/memoryurl.js.map +1 -0
  272. package/dist/src/tui/model.js +25 -0
  273. package/dist/src/tui/model.js.map +1 -1
  274. package/dist/src/tui/prpause.js +7 -0
  275. package/dist/src/tui/prpause.js.map +1 -1
  276. package/dist/src/tui/remote.js +7 -1
  277. package/dist/src/tui/remote.js.map +1 -1
  278. package/dist/src/tui/route.js +143 -0
  279. package/dist/src/tui/route.js.map +1 -0
  280. package/dist/src/tui/setupstate.js +32 -0
  281. package/dist/src/tui/setupstate.js.map +1 -0
  282. package/dist/src/tui/sshtriage.js +102 -5
  283. package/dist/src/tui/sshtriage.js.map +1 -1
  284. package/dist/src/tui/theme.js +2 -1
  285. package/dist/src/tui/theme.js.map +1 -1
  286. package/dist/src/tui/unwedge.js +2 -12
  287. package/dist/src/tui/unwedge.js.map +1 -1
  288. package/dist/src/verdicts.js +17 -5
  289. package/dist/src/verdicts.js.map +1 -1
  290. package/dist/src/version.js +1 -1
  291. package/dist/src/version.js.map +1 -1
  292. package/dist/src/webhook.js +12 -1
  293. package/dist/src/webhook.js.map +1 -1
  294. package/dist/src/wire.js +34 -8
  295. package/dist/src/wire.js.map +1 -1
  296. package/dist/src/workerworld.js +11 -1
  297. package/dist/src/workerworld.js.map +1 -1
  298. package/docker/README.md +1 -1
  299. package/docker/companion/entrypoint.sh +20 -1
  300. package/docker/entrypoint.sh +57 -6
  301. package/docker-compose.yml +16 -1
  302. package/extensions/cli/home.ts +14 -0
  303. package/extensions/cli/login-route.ts +10 -0
  304. package/extensions/cli/selfimprove.ts +18 -0
  305. package/extensions/home.ts +484 -0
  306. package/extensions/login-route.ts +220 -0
  307. package/extensions/onboarding.ts +41 -11
  308. package/extensions/selfimprove.ts +65 -0
  309. package/extensions/setup-scan.ts +79 -766
  310. package/extensions/setup.ts +14 -70
  311. package/package.json +6 -4
  312. package/prompts/onboarding.md +58 -18
  313. package/schemas/review-result.v1.json +60 -0
  314. package/scripts/deploy.sh +8 -6
  315. package/scripts/mint-token.sh +25 -4
  316. package/scripts/qcr-host.sh +28 -5
  317. package/scripts/vm-ghcr-login.sh +7 -4
  318. package/src/archive/restore.ts +18 -21
  319. package/src/botworker.ts +13 -2
  320. package/src/channelfs.ts +3 -26
  321. package/src/ciretry.ts +2 -1
  322. package/src/cli.ts +59 -67
  323. package/src/cliops.ts +3 -2
  324. package/src/commands.ts +28 -2
  325. package/src/config.ts +62 -4
  326. package/src/daemon.ts +58 -7
  327. package/src/debuground.ts +3 -0
  328. package/src/debugroundpi.ts +12 -1
  329. package/src/dispatch.ts +5 -4
  330. package/src/engine/caserunner-sdk.ts +29 -314
  331. package/src/engine/debugpi.ts +17 -5
  332. package/src/execboundary.ts +16 -4
  333. package/src/github.ts +57 -15
  334. package/src/githubtoken.ts +435 -0
  335. package/src/home/adapters.ts +418 -0
  336. package/src/home/advanced.ts +421 -0
  337. package/src/home/advancedtools.ts +96 -0
  338. package/src/home/autoreview.ts +385 -0
  339. package/src/home/daemonstatus.ts +55 -0
  340. package/src/home/devicelogin.ts +107 -0
  341. package/src/home/launch.ts +257 -0
  342. package/src/home/loginowner.ts +248 -0
  343. package/src/home/memory.ts +176 -0
  344. package/src/home/model.ts +965 -0
  345. package/src/home/panel.ts +666 -0
  346. package/src/home/ports.ts +338 -0
  347. package/src/home/review.ts +206 -0
  348. package/src/home/reviews.ts +565 -0
  349. package/src/home/setup.ts +689 -0
  350. package/src/home/setupadapters.ts +217 -0
  351. package/src/home/setupcard.ts +288 -0
  352. package/src/home/setuptools.ts +57 -0
  353. package/src/home/spend.ts +194 -0
  354. package/src/home/switches.ts +293 -0
  355. package/src/home/switchon.ts +554 -0
  356. package/src/home/toolnames.ts +46 -0
  357. package/src/home/tune.ts +405 -0
  358. package/src/home/words.ts +8 -0
  359. package/src/homes.ts +17 -0
  360. package/src/install.ts +1 -1
  361. package/src/ipc/workerspec.ts +15 -0
  362. package/src/live/publish.ts +4 -1
  363. package/src/livecli.ts +6 -1
  364. package/src/loginhint.ts +8 -3
  365. package/src/loginrefresh/handover.ts +138 -0
  366. package/src/loginrefresh/lock.ts +72 -3
  367. package/src/loginrefresh/pirefresh.ts +3 -0
  368. package/src/loginrefresh/refresher.ts +34 -5
  369. package/src/loginrefresh/supervisor.ts +25 -2
  370. package/src/npmverbs.ts +55 -13
  371. package/src/onboard/choices.ts +40 -4
  372. package/src/onboard/engine.ts +70 -421
  373. package/src/onboard/pipin.ts +31 -0
  374. package/src/onboard/stepforward.ts +61 -0
  375. package/src/onboard/steps/appslug.ts +4 -1
  376. package/src/onboard/steps/bringup.ts +3 -2
  377. package/src/onboard/steps/compose.ts +31 -0
  378. package/src/onboard/steps/configfill.ts +144 -40
  379. package/src/onboard/steps/githubaccount.ts +716 -0
  380. package/src/onboard/steps/githubapp.ts +92 -8
  381. package/src/onboard/steps/golive.ts +2 -0
  382. package/src/onboard/steps/index.ts +11 -0
  383. package/src/onboard/steps/ingress.ts +98 -31
  384. package/src/onboard/steps/llmauth.ts +114 -29
  385. package/src/onboard/steps/loginshare.ts +258 -0
  386. package/src/onboard/steps/memory.ts +76 -3
  387. package/src/onboard/steps/proof.ts +473 -55
  388. package/src/onboard/steps/repoconfig.ts +85 -0
  389. package/src/onboard/steps/topology.ts +7 -6
  390. package/src/onboard/steps/transport.ts +15 -0
  391. package/src/onboard/steps/tuiroute.ts +119 -0
  392. package/src/onboard/steps/verifyup.ts +20 -1
  393. package/src/onboard/toolnames.ts +34 -0
  394. package/src/operatorwrite.ts +25 -0
  395. package/src/outcomes.ts +8 -1
  396. package/src/piagentdir.ts +12 -0
  397. package/src/pilogin.ts +32 -16
  398. package/src/posting-journal.ts +6 -1
  399. package/src/posting-outbox.ts +10 -2
  400. package/src/posting-receipt.ts +17 -0
  401. package/src/qcrbin.ts +16 -11
  402. package/src/recheck.ts +2 -1
  403. package/src/reconcile.ts +29 -3
  404. package/src/reviewargs.ts +119 -0
  405. package/src/reviewcli.ts +6 -167
  406. package/src/reviewdetach.ts +107 -13
  407. package/src/reviewhistory.ts +20 -6
  408. package/src/reviewnow.ts +76 -0
  409. package/src/scancli.ts +75 -826
  410. package/src/selfimprove.ts +90 -0
  411. package/src/setup/adapters.ts +13 -221
  412. package/src/setup/caserunner.ts +34 -273
  413. package/src/setup/engine.ts +62 -496
  414. package/src/setup/facts.ts +3 -627
  415. package/src/setup/findings.ts +3 -1264
  416. package/src/setup/presentation.ts +3 -18
  417. package/src/setup/scan.ts +3 -1033
  418. package/src/setup/scanbuild.ts +20 -114
  419. package/src/setup/scanlease.ts +3 -297
  420. package/src/setup/scanreport.ts +3 -373
  421. package/src/setup/scanrun.ts +3 -2268
  422. package/src/setup/scantree.ts +3 -259
  423. package/src/setup/stage2request.ts +3 -347
  424. package/src/setup/unitcut.ts +3 -523
  425. package/src/state.ts +43 -0
  426. package/src/statemachine.ts +4 -1
  427. package/src/takeover.ts +5 -0
  428. package/src/takeoverpi.ts +16 -2
  429. package/src/tui/app.ts +47 -0
  430. package/src/tui/board.ts +111 -14
  431. package/src/tui/cmdbar.ts +4 -1
  432. package/src/tui/companion.ts +80 -11
  433. package/src/tui/connect.ts +7 -1
  434. package/src/tui/deploytab.ts +2 -2
  435. package/src/tui/filters.ts +7 -3
  436. package/src/tui/ghread.ts +74 -7
  437. package/src/tui/index.ts +117 -23
  438. package/src/tui/memforward.ts +52 -3
  439. package/src/tui/memory.ts +3 -15
  440. package/src/tui/memoryurl.ts +22 -0
  441. package/src/tui/model.ts +36 -2
  442. package/src/tui/prpause.ts +11 -2
  443. package/src/tui/remote.ts +9 -1
  444. package/src/tui/route.ts +173 -0
  445. package/src/tui/setupstate.ts +56 -0
  446. package/src/tui/sshtriage.ts +127 -6
  447. package/src/tui/theme.ts +2 -1
  448. package/src/tui/unwedge.ts +2 -14
  449. package/src/verdicts.ts +23 -5
  450. package/src/version.ts +1 -1
  451. package/src/webhook.ts +10 -1
  452. package/src/wire.ts +42 -9
  453. package/src/workerworld.ts +16 -2
  454. package/dist/src/live/projection.js +0 -450
  455. package/dist/src/live/projection.js.map +0 -1
  456. package/dist/src/live/read.js +0 -60
  457. package/dist/src/live/read.js.map +0 -1
  458. package/dist/src/onboard/launch.js +0 -83
  459. package/dist/src/onboard/launch.js.map +0 -1
  460. package/dist/src/setup/ledger.js +0 -207
  461. package/dist/src/setup/ledger.js.map +0 -1
  462. package/dist/src/setup/stage2.js +0 -617
  463. package/dist/src/setup/stage2.js.map +0 -1
  464. package/dist/src/setup/textsniff.js +0 -89
  465. package/dist/src/setup/textsniff.js.map +0 -1
  466. package/src/live/projection.ts +0 -491
  467. package/src/live/read.ts +0 -86
  468. package/src/onboard/launch.ts +0 -115
  469. package/src/setup/ledger.ts +0 -324
  470. package/src/setup/stage2.ts +0 -926
  471. package/src/setup/textsniff.ts +0 -95
package/src/live/read.ts DELETED
@@ -1,86 +0,0 @@
1
- // Finding and reading a round's LiveEvent logs: what `qcr review status` and
2
- // `qcr live` do with a run directory. No writer and no engine here, so a
3
- // daemon-side or TUI reader can import it.
4
- import { readdirSync, statSync } from "node:fs";
5
- import { join } from "node:path";
6
- import {
7
- LIVE_MAIN_FILE,
8
- readLiveEvents,
9
- type LiveEvent,
10
- } from "@quickchatai/pi-quickreview/src/live/events.js";
11
- import { projectRoundProgress } from "./projection.js";
12
- import type { RoundProgressSnapshot } from "@quickchatai/pi-quickreview/src/round-progress.js";
13
-
14
- export interface LiveRoundRef {
15
- /** `state/live/<key>/r<round>` */
16
- dir: string;
17
- key: string;
18
- round: number;
19
- }
20
-
21
- const names = (dir: string): string[] => {
22
- try {
23
- return readdirSync(dir);
24
- } catch {
25
- return [];
26
- }
27
- };
28
-
29
- /** Every round log directory under `<root>/state/live/`, oldest round first. */
30
- export function listLiveRounds(root: string): LiveRoundRef[] {
31
- const base = join(root, "state", "live");
32
- const found: LiveRoundRef[] = [];
33
- for (const key of names(base)) {
34
- for (const r of names(join(base, key))) {
35
- const m = /^r(\d+)$/.exec(r);
36
- if (m) found.push({ dir: join(base, key, r), key, round: Number(m[1]) });
37
- }
38
- }
39
- return found.sort((a, b) => a.key.localeCompare(b.key) || a.round - b.round);
40
- }
41
-
42
- function mtime(path: string): number {
43
- try {
44
- return statSync(path).mtimeMs;
45
- } catch {
46
- return 0;
47
- }
48
- }
49
-
50
- /** The round that was written last. */
51
- export function latestLiveRound(root: string): LiveRoundRef | undefined {
52
- return listLiveRounds(root).reduce<LiveRoundRef | undefined>(
53
- (best, r) =>
54
- best === undefined ||
55
- mtime(join(r.dir, LIVE_MAIN_FILE)) >= mtime(join(best.dir, LIVE_MAIN_FILE))
56
- ? r
57
- : best,
58
- undefined,
59
- );
60
- }
61
-
62
- /** The ids of the lens sessions a round logged, in file-name order. */
63
- export function lensLogIds(dir: string): string[] {
64
- return names(dir)
65
- .flatMap((f) => /^lens-(.+)\.jsonl$/.exec(f)?.[1] ?? [])
66
- .sort();
67
- }
68
-
69
- export interface RoundView {
70
- ref: LiveRoundRef;
71
- events: readonly LiveEvent[];
72
- /** The round-progress v2 snapshot the main log projects to. */
73
- snapshot: RoundProgressSnapshot | undefined;
74
- lenses: string[];
75
- }
76
-
77
- /** Read one round's main log and project it. */
78
- export function viewRound(ref: LiveRoundRef): RoundView {
79
- const { events } = readLiveEvents(join(ref.dir, LIVE_MAIN_FILE));
80
- return {
81
- ref,
82
- events,
83
- snapshot: projectRoundProgress(events)?.snapshot,
84
- lenses: lensLogIds(ref.dir),
85
- };
86
- }
@@ -1,115 +0,0 @@
1
- // `qcr onboard` mechanics: launch pi at the pinned version via npx
2
- // (pi stays a non-dependency — this works from a bare clone with only Node and
3
- // the checkout), hand it the onboarding system prompt + exactly one onboarding
4
- // extension + a restricted built-in tool set, and on exit either exec the TUI
5
- // (checklist complete) or say how to resume.
6
- //
7
- // Everything effectful is injected (spawn, completion read, log), so tests
8
- // drive the whole flow with fakes; src/cli.ts cmdOnboard is the one real
9
- // assembly.
10
- import { readFileSync } from "node:fs";
11
- import { join } from "node:path";
12
-
13
- export interface PiPin {
14
- package: string;
15
- version: string;
16
- }
17
-
18
- /** The pin from package.json's "qcrPi" block — the mirror that exists on a bare
19
- * public clone, where config.toml does not (the wizard writes it later).
20
- * config.toml [pi] stays the preferred source when it loads (cmdOnboard tries
21
- * it first); both mirror the same pinned version by rule 7's discipline. */
22
- export function piPinFromPackageJson(
23
- root: string,
24
- readFile: (p: string) => string = (p) => readFileSync(p, "utf8"),
25
- ): PiPin | null {
26
- try {
27
- const pkg = JSON.parse(readFile(join(root, "package.json"))) as {
28
- qcrPi?: { package?: unknown; version?: unknown };
29
- };
30
- const pi = pkg.qcrPi;
31
- if (pi && typeof pi.package === "string" && typeof pi.version === "string") {
32
- return { package: pi.package, version: pi.version };
33
- }
34
- } catch {
35
- // fall through — the caller reports the missing pin
36
- }
37
- return null;
38
- }
39
-
40
- /** Dedicated onboarding session store — never the reviewer/companion dirs, so
41
- * onboarding cannot pollute (or be polluted by) any other pi session store. */
42
- export function onboardingSessionDir(root: string): string {
43
- return join(root, "state", "onboarding", "pi-sessions");
44
- }
45
-
46
- /**
47
- * The full npx argv for the onboarding pi session.
48
- *
49
- * `--approve` is the same never-block-on-input rule as buildPiCommand
50
- * (src/sessions.ts) and the companion's piArgv: the wizard must never stall
51
- * on pi's interactive "Trust project folder?" modal.
52
- *
53
- * VERIFY-ON-PIN: `--no-extensions`, `--no-skills`, `--no-context-files`,
54
- * `-e <path>` and `--tools <list>` against the pinned pi version's --help —
55
- * the intent (no ambient extensions, exactly one onboarding extension, a
56
- * read-only built-in belt) is the design decision; the exact flag spellings
57
- * are unverified statically and belong to the named onboarding smoke step.
58
- */
59
- export function onboardingPiArgv(pin: PiPin, root: string): string[] {
60
- return [
61
- "npx",
62
- "-y",
63
- `${pin.package}@${pin.version}`,
64
- "--approve",
65
- "--no-extensions",
66
- "--no-skills",
67
- "--no-context-files",
68
- "-e",
69
- join(root, "extensions", "cli", "onboarding.ts"),
70
- "--system-prompt",
71
- join(root, "prompts", "onboarding.md"),
72
- "--tools",
73
- "read,ls,grep",
74
- "--session-dir",
75
- onboardingSessionDir(root),
76
- ];
77
- }
78
-
79
- export interface OnboardRunDeps {
80
- pin: PiPin;
81
- root: string;
82
- /** Spawn with stdio inherited; resolves with the exit code (a spawn failure
83
- * is a nonzero code, never a throw). */
84
- spawn(cmd: string, args: string[]): Promise<number>;
85
- /** Does the checklist file say every step verified? (engine.checklistComplete
86
- * over state/ — the finish tool only sets the flag after a green re-verify.) */
87
- isComplete(): boolean;
88
- log(line: string): void;
89
- /** Compiled CLI path for the TUI handoff (dist/src/cli.js). */
90
- cliPath: string;
91
- /** Node binary for the handoff; defaults to the running one. */
92
- nodeBin?: string;
93
- }
94
-
95
- /**
96
- * Run the onboarding session and handle the handoff:
97
- * - checklist complete after pi exits ⇒ open the TUI (same stdio);
98
- * - otherwise print how to resume — `qcr onboard` again re-verifies
99
- * completed steps and continues at the first unfinished one.
100
- */
101
- export async function runOnboard(deps: OnboardRunDeps): Promise<number> {
102
- const argv = onboardingPiArgv(deps.pin, deps.root);
103
- deps.log(
104
- `onboarding: launching pi ${deps.pin.package}@${deps.pin.version} via npx (first run downloads it — give it a moment)`,
105
- );
106
- const code = await deps.spawn(argv[0]!, argv.slice(1));
107
- if (deps.isComplete()) {
108
- deps.log("onboarding: checklist verified complete — opening the TUI.");
109
- return deps.spawn(deps.nodeBin ?? process.execPath, [deps.cliPath, "tui"]);
110
- }
111
- deps.log(
112
- "onboarding: not finished yet. Run `qcr onboard` again to resume — completed steps are re-verified, not repeated.",
113
- );
114
- return code;
115
- }
@@ -1,324 +0,0 @@
1
- // The COVERAGE LEDGER (spec "Ledger"): what the
2
- // scan has read — one row per file + content hash + lens, with the tier,
3
- // effort, validator setting, scan sha and cost it was read at. Two jobs:
4
- //
5
- // * the RESUME GRAIN — stage 1 never re-reads a file whose NEWEST ledger
6
- // row for the lens is at the same content hash, so an interrupted scan (a TUI
7
- // quit, an expired login, a stop, the hard stop) continues where it
8
- // stopped and a finished one costs nothing to start again;
9
- // * the PRICE — after the first-scan calibration run, every cost preview is
10
- // measured from these rows' actual costs, never from a constant.
11
- //
12
- // A file too big for one case is read in LINE RANGES (scan.ts cutRanges):
13
- // one row per range, carrying it, so a file is covered only when the ranges
14
- // of its newest reading cover every line — a run cut short between ranges
15
- // leaves the rest to read, and changed content re-reads every range.
16
- //
17
- // A row records the DEPTH it was read at (`[scan] depth`; absent = standard,
18
- // so a ledger written before depths reads as standard). A deep reading is its
19
- // own coverage: a standard reading of a file never covers it for a deep run
20
- // (a deep run on a scanned repo would otherwise read nothing), while a deep
21
- // reading covers it for a standard one. A standard row and a deep row of the
22
- // same file, content and lens stand side by side.
23
- //
24
- // A row is written only when its case FINISHED — a case cut short leaves no
25
- // row, so it is read again in full. A case's cost is apportioned over its
26
- // files by lines; a case the runner could not price writes null costs, which
27
- // the rate ignores (it never reads as free).
28
- //
29
- // Persistence: `<state home>/scan/ledger.json`, tmp+rename atomic through the
30
- // state port. An unreadable ledger is REPORTED and refuses writes: it is the
31
- // record of money already spent, and starting over on top of it would pay
32
- // for the same files twice.
33
- import type { ScanDepth } from "../config.js";
34
- import type { StateFs } from "./engine.js";
35
- import { coverKey, rangesCover, type FileRange, type ScanRate } from "./scan.js";
36
-
37
- export const LEDGER_FILE = "scan/ledger.json";
38
- export const LEDGER_VERSION = 1;
39
-
40
- export interface LedgerEntry {
41
- path: string;
42
- /** The file's git blob hash at `sha`. */
43
- blob: string;
44
- lens: string;
45
- /** The line range this row read, when the file was cut into ranges;
46
- * absent (or null) = the whole file. Part of the row's identity. */
47
- range?: FileRange | null;
48
- tier: string;
49
- effort: string;
50
- /** Whether validation was enabled for this case; a lens returning NONE needs no validator run. */
51
- validator: boolean;
52
- /** `"deep"` for a deep scan's reading; absent = standard. */
53
- depth?: ScanDepth;
54
- sha: string;
55
- /** Lines read: the file's, or its range's. */
56
- lines: number;
57
- /** This file's share of its case's cost; null when the runner reported
58
- * none. */
59
- cost_usd: number | null;
60
- run: string;
61
- /** The case, within the run (`<unit>` or `<unit>#<i>`). */
62
- case: string;
63
- at: string;
64
- }
65
-
66
- /** What a price is measured over: the same lens at the same settings — at
67
- * one depth when `depth` is given, at any otherwise. */
68
- export interface RateKey {
69
- lens: string;
70
- tier: string;
71
- effort: string;
72
- validator: boolean;
73
- depth?: ScanDepth;
74
- }
75
-
76
- /** The depth a row was read at. */
77
- export const depthOf = (e: Pick<LedgerEntry, "depth">): ScanDepth =>
78
- e.depth === "deep" ? "deep" : "standard";
79
-
80
- export interface LedgerFile {
81
- version: number;
82
- entries: LedgerEntry[];
83
- }
84
-
85
- const entryKey = (e: Pick<LedgerEntry, "path" | "blob" | "lens" | "range" | "depth">): string =>
86
- `${e.lens}\0${coverKey(e.path, e.blob)}${e.range ? `\0${e.range.start}-${e.range.end}` : ""}${depthOf(e) === "deep" ? "\0deep" : ""}`;
87
-
88
- export class Ledger {
89
- constructor(private readonly state: StateFs) {}
90
-
91
- /** The ledger, or why it cannot be read (then `entries` is empty and
92
- * UNKNOWN, not absent). */
93
- load(): { entries: LedgerEntry[]; error: string | null } {
94
- let text: string | null;
95
- try {
96
- text = this.state.read(LEDGER_FILE);
97
- } catch (err) {
98
- return {
99
- entries: [],
100
- error: `${LEDGER_FILE} in ${this.state.location} cannot be read: ${String(err)}`,
101
- };
102
- }
103
- if (text === null) return { entries: [], error: null };
104
- try {
105
- const raw = JSON.parse(text) as Partial<LedgerFile> | null;
106
- if (
107
- typeof raw !== "object" ||
108
- raw === null ||
109
- raw.version !== LEDGER_VERSION ||
110
- !Array.isArray(raw.entries)
111
- ) {
112
- throw new Error("not a version-1 ledger");
113
- }
114
- for (const e of raw.entries) {
115
- if (
116
- typeof e?.path !== "string" ||
117
- typeof e.blob !== "string" ||
118
- typeof e.lens !== "string"
119
- ) {
120
- throw new Error("an entry has no path, blob or lens");
121
- }
122
- }
123
- return { entries: raw.entries, error: null };
124
- } catch (err) {
125
- return {
126
- entries: [],
127
- error:
128
- `${LEDGER_FILE} in ${this.state.location} is not a coverage ledger this version reads (${String(err)}) — ` +
129
- "nothing will overwrite it; move it aside to start the ledger over",
130
- };
131
- }
132
- }
133
-
134
- /** Add (or replace, by file + content hash + lens + range + depth)
135
- * finished rows. Throws when the stored ledger cannot be read — never
136
- * writes over it. */
137
- record(rows: readonly LedgerEntry[]): void {
138
- const { entries, error } = this.load();
139
- if (error !== null) throw new Error(error);
140
- const byKey = new Map(entries.map((e) => [entryKey(e), e]));
141
- for (const r of rows) byKey.set(entryKey(r), r);
142
- const file: LedgerFile = { version: LEDGER_VERSION, entries: [...byKey.values()] };
143
- this.state.write(LEDGER_FILE, `${JSON.stringify(file)}\n`);
144
- }
145
- }
146
-
147
- /** `path\0blob` of each file's NEWEST reading by `lens` — the same newest
148
- * row `FindingsStore.reconcile` settles findings against — complete at
149
- * `depth` (`readings`). An older reading a later one superseded covers
150
- * nothing: its findings may since have been settled `fixed`, so content that
151
- * comes back to it (a revert) must be read again for them to come back too. */
152
- export function coveredBy(
153
- entries: readonly LedgerEntry[],
154
- lens: string,
155
- depth: ScanDepth = "standard",
156
- ): Set<string> {
157
- const out = new Set<string>();
158
- for (const [path, r] of readings(entries, lens, depth))
159
- if (r.complete) out.add(coverKey(path, r.blob));
160
- return out;
161
- }
162
-
163
- /** A file's newest reading by one lens: the rows at its newest content,
164
- * back to the last row at other content (an older reading of the same
165
- * content, superseded since, is not part of it). */
166
- export interface Reading {
167
- blob: string;
168
- /** The newest row of the reading. */
169
- newest: LedgerEntry;
170
- /** Whole file, or ranges covering every line. */
171
- complete: boolean;
172
- /** The ranges read so far (empty when a whole-file row stands). */
173
- ranges: FileRange[];
174
- /** Every run that wrote a row of this reading — a ranged reading cut short
175
- * is finished by a later run, and both re-read the file. */
176
- runs: string[];
177
- }
178
-
179
- /** Every path's newest reading by `lens`, as `depth` counts it. The newest
180
- * content is settled by every row whatever its depth (a later reading of
181
- * other content supersedes a deep one too); within it, a `"standard"` reading
182
- * is made of every row — a deep reading is at least a standard one — and a
183
- * `"deep"` reading of the deep rows alone, so a path whose newest content was
184
- * read only at standard depth has no deep reading. */
185
- export function readings(
186
- entries: readonly LedgerEntry[],
187
- lens: string,
188
- depth: ScanDepth = "standard",
189
- ): Map<string, Reading> {
190
- const byPath = new Map<string, LedgerEntry[]>();
191
- for (const e of entries) {
192
- if (e.lens !== lens) continue;
193
- const rows = byPath.get(e.path);
194
- if (rows) rows.push(e);
195
- else byPath.set(e.path, [e]);
196
- }
197
- const out = new Map<string, Reading>();
198
- for (const [path, rows] of byPath) {
199
- // Stable by time: equal stamps keep ledger order, as `newestRead` does.
200
- const sorted = rows
201
- .map((e, i) => ({ e, i }))
202
- .sort((a, b) => compareAt(a.e.at, b.e.at) || a.i - b.i);
203
- const newest = sorted[sorted.length - 1]!.e;
204
- const all: LedgerEntry[] = [];
205
- for (let k = sorted.length - 1; k >= 0 && sorted[k]!.e.blob === newest.blob; k--)
206
- all.push(sorted[k]!.e);
207
- const streak = depth === "deep" ? all.filter((e) => depthOf(e) === "deep") : all;
208
- if (streak.length === 0) continue;
209
- const whole = streak.some((e) => !e.range);
210
- const ranges = streak.filter((e) => e.range).map((e) => e.range!);
211
- const total = ranges[0]?.total ?? 0;
212
- out.set(path, {
213
- blob: newest.blob,
214
- newest: streak[0]!,
215
- complete: whole || (ranges.length > 0 && rangesCover(ranges, 1, total)),
216
- ranges: whole ? [] : ranges,
217
- runs: [...new Set(streak.map((e) => e.run))],
218
- });
219
- }
220
- return out;
221
- }
222
-
223
- /** `path\0blob` → the ranges of each newest reading by `lens` at `depth` that
224
- * is not yet complete: the ranges a plan need not read again. */
225
- export function partialRanges(
226
- entries: readonly LedgerEntry[],
227
- lens: string,
228
- depth: ScanDepth = "standard",
229
- ): Map<string, FileRange[]> {
230
- const out = new Map<string, FileRange[]>();
231
- for (const [path, r] of readings(entries, lens, depth))
232
- if (!r.complete && r.ranges.length > 0) out.set(coverKey(path, r.blob), r.ranges);
233
- return out;
234
- }
235
-
236
- /** One case's identity across its rows: the run it ran in and its id there. */
237
- const caseId = (e: LedgerEntry): string => `${e.run}\0${e.case}`;
238
-
239
- /** Was `e` written at `key`'s settings — its depth too, when the key names one? */
240
- function atKey(e: LedgerEntry, key: RateKey): boolean {
241
- return (
242
- e.lens === key.lens &&
243
- e.tier === key.tier &&
244
- e.effort === key.effort &&
245
- e.validator === key.validator &&
246
- (key.depth === undefined || depthOf(e) === key.depth)
247
- );
248
- }
249
-
250
- /** Rows written for `key` at all, and how many of their cases were priced —
251
- * which is how a preview tells "never calibrated" from "calibrated, but the
252
- * runner reports no cost". */
253
- export function casesFor(
254
- entries: readonly LedgerEntry[],
255
- key: RateKey,
256
- ): { cases: number; priced: number } {
257
- const cases = new Map<string, boolean>();
258
- for (const e of entries) {
259
- if (!atKey(e, key)) continue;
260
- const id = caseId(e);
261
- cases.set(id, (cases.get(id) ?? false) || e.cost_usd !== null);
262
- }
263
- return { cases: cases.size, priced: [...cases.values()].filter(Boolean).length };
264
- }
265
-
266
- /** The measured price for `key`: every priced row's cost over its lines, or
267
- * null when no priced case has finished at these settings. */
268
- export function measuredRate(entries: readonly LedgerEntry[], key: RateKey): ScanRate | null {
269
- return rateWhere(entries, (e) => atKey(e, key));
270
- }
271
-
272
- /** The measured price over the priced rows `keep` selects (stage 2 borrows
273
- * another lens's price at the same settings this way), or null when none. */
274
- export function rateWhere(
275
- entries: readonly LedgerEntry[],
276
- keep: (e: LedgerEntry) => boolean,
277
- ): ScanRate | null {
278
- let cost = 0;
279
- let lines = 0;
280
- const cases = new Set<string>();
281
- for (const e of entries) {
282
- if (!keep(e) || e.cost_usd === null) continue;
283
- cost += e.cost_usd;
284
- // The file's REAL lines — the count the preview showed. An empty file
285
- // (an `__init__.py`) is 0 here as there; `Math.max(lines, 1)` belongs to
286
- // splitting a case's cost across its rows, never to the rate's basis.
287
- lines += e.lines;
288
- cases.add(caseId(e));
289
- }
290
- if (cases.size === 0 || lines === 0) return null;
291
- return { usd_per_kline: (cost / lines) * 1000, cases: cases.size, lines, cost_usd: cost };
292
- }
293
-
294
- // ---- the delta's basis ----
295
-
296
- /** Timestamp order, the one rule `readings` and `newestRead` both sort by. */
297
- function compareAt(a: string, b: string): number {
298
- return a < b ? -1 : a > b ? 1 : 0;
299
- }
300
-
301
- /** Each path's NEWEST content hash read by `lens` — the reading findings
302
- * are settled against. A path whose content is not that one has CHANGED
303
- * since it was scanned, even when an older, superseded reading saw exactly
304
- * this content (a revert): that reading's findings may since be `fixed`. */
305
- export function newestRead(entries: readonly LedgerEntry[], lens: string): Map<string, string> {
306
- const newest = new Map<string, LedgerEntry>();
307
- for (const e of entries) {
308
- if (e.lens !== lens) continue;
309
- const had = newest.get(e.path);
310
- if (!had || compareAt(e.at, had.at) >= 0) newest.set(e.path, e);
311
- }
312
- return new Map([...newest].map(([path, e]) => [path, e.blob]));
313
- }
314
-
315
- /** The sha of the LAST scan by `lens` — its newest row's — or null when the
316
- * ledger holds none: what a delta re-scan compares the tree against. */
317
- export function lastScanned(
318
- entries: readonly LedgerEntry[],
319
- lens: string,
320
- ): { sha: string; at: string; run: string } | null {
321
- let last: LedgerEntry | null = null;
322
- for (const e of entries) if (e.lens === lens && (last === null || e.at >= last.at)) last = e;
323
- return last === null ? null : { sha: last.sha, at: last.at, run: last.run };
324
- }