@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
@@ -1,259 +1,3 @@
1
- // The scan's source port: the reviewed checkout, read-only, as trees at a
2
- // chosen sha.
3
- //
4
- // "Scan my local checkout at a sha I choose" means the tree AT THAT SHA, not
5
- // whatever the working copy holds: the file list and every content hash come
6
- // from git's own objects (`ls-tree` — the blob hash IS the ledger's content
7
- // hash), and the files the lenses read are materialised from exactly those
8
- // blobs (`git cat-file --batch`, raw bytes) into
9
- // `<state home>/scan/tree/<sha>/`. Not `git archive`: it honours in-tree
10
- // `export-ignore`/`export-subst`, so a listed file could be missing or read
11
- // as bytes its blob hash never named; and it extracts symlinks. Only the
12
- // listed regular files are written, so nothing in the tree is a link. The
13
- // checkout itself is never written — the companion mounts it `:ro` — and no
14
- // credential is involved: the objects are local.
15
- //
16
- // One materialised tree is kept per sha; opening another removes the earlier
17
- // ones except those `keep` names (the one a running scan is reading). A tree
18
- // is complete only once its marker is written, so an extraction cut short is
19
- // redone rather than trusted. The marker is a SIBLING of the tree
20
- // (`<sha>.complete`), never a path inside it: a name inside the tree is one
21
- // the commit can occupy.
22
- import { execFileSync } from "node:child_process";
23
- import {
24
- existsSync,
25
- mkdirSync,
26
- readdirSync,
27
- readFileSync,
28
- rmSync,
29
- statSync,
30
- writeFileSync,
31
- } from "node:fs";
32
- import { dirname, isAbsolute, join, normalize, relative } from "node:path";
33
- import { inRoot, MAX_READ_BYTES } from "./adapters.js";
34
- import type { ScanSource, ScanTree } from "./scanrun.js";
35
- import type { UnreadableReason } from "./scan.js";
36
- import { isBinaryContent, textLineCount } from "./textsniff.js";
37
- import type { TreeFile } from "./scan.js";
38
-
39
- /** Where materialised trees live, under the state home. */
40
- export const SCAN_TREES_DIR = "scan/tree";
41
- const COMPLETE_SUFFIX = ".complete";
42
- /** Blobs, and their bytes, per `cat-file --batch` call — bound one call's
43
- * output buffer (a single larger blob is a call of its own). */
44
- const BLOBS_PER_BATCH = 256;
45
- const BYTES_PER_BATCH = 64 * 1024 * 1024;
46
- const SHA_RE = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
47
- /** A full commit sha (SHA-1 or SHA-256) — the only spelling a tree is opened at. */
48
- export const isFullSha = (s: string): boolean => SHA_RE.test(s);
49
-
50
- /** `treesDir` (relative to the state home) is where this source keeps its
51
- * trees — the scan's own by default; another reader of trees (the PR
52
- * replay) passes its own, so neither prunes the other's.
53
- * `batchBytes` bounds one `cat-file --batch` call's output (tests shrink it). */
54
- export function gitScanSource(
55
- repoRoot: string,
56
- stateRoot: string,
57
- opts: { treesDir?: string; batchBytes?: number } = {},
58
- ): ScanSource {
59
- const batchBytes = opts.batchBytes ?? BYTES_PER_BATCH;
60
- // Why the last git call failed — its stderr's first line, or the error
61
- // (a buffer overrun, git missing) — so a refusal names the cause.
62
- let lastFailure = "";
63
- const failed = (err: unknown): null => {
64
- const e = err as { stderr?: Buffer | string; message?: string };
65
- const stderr = (
66
- Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf8") : (e.stderr ?? "")
67
- ).trim();
68
- lastFailure = (stderr.split("\n")[0] || e.message || String(err)).slice(0, 300);
69
- return null;
70
- };
71
- const because = (): string => (lastFailure ? ` (${lastFailure})` : "");
72
- const git = (args: string[], maxBuffer = 64 * 1024 * 1024): string | null => {
73
- try {
74
- return execFileSync("git", ["-c", `safe.directory=${repoRoot}`, "-C", repoRoot, ...args], {
75
- encoding: "utf8",
76
- maxBuffer,
77
- stdio: ["ignore", "pipe", "pipe"],
78
- });
79
- } catch (err) {
80
- return failed(err);
81
- }
82
- };
83
- const gitBytes = (args: string[], input: string, maxBuffer: number): Buffer | null => {
84
- try {
85
- return execFileSync("git", ["-c", `safe.directory=${repoRoot}`, "-C", repoRoot, ...args], {
86
- input,
87
- maxBuffer,
88
- stdio: ["pipe", "pipe", "pipe"],
89
- });
90
- } catch (err) {
91
- return failed(err);
92
- }
93
- };
94
- /** Write every listed file's blob, raw, under `root`. False when git
95
- * cannot produce one — the tree is then not complete. */
96
- const materialise = (root: string, files: TreeFile[]): boolean => {
97
- const pathsOf = new Map<string, string[]>();
98
- for (const f of files) pathsOf.set(f.blob, [...(pathsOf.get(f.blob) ?? []), f.path]);
99
- const blobs = [...pathsOf.keys()];
100
- if (blobs.length === 0) return true;
101
- // Sizes first, so one call's output is bounded by BYTES as well as by
102
- // count: a repository of large assets must not need one huge buffer.
103
- const checked = gitBytes(
104
- ["cat-file", "--batch-check"],
105
- blobs.join("\n") + "\n",
106
- 64 * 1024 * 1024,
107
- );
108
- if (checked === null) return false;
109
- const sizeOf = new Map<string, number>();
110
- for (const line of checked.toString("utf8").split("\n")) {
111
- const m = /^([0-9a-f]+) blob (\d+)$/.exec(line);
112
- if (m) sizeOf.set(m[1]!, Number(m[2]));
113
- }
114
- const chunks: Array<{ blobs: string[]; bytes: number }> = [];
115
- for (const blob of blobs) {
116
- const size = sizeOf.get(blob);
117
- if (size === undefined) return false;
118
- const last = chunks[chunks.length - 1];
119
- if (last && last.blobs.length < BLOBS_PER_BATCH && last.bytes + size <= batchBytes) {
120
- last.blobs.push(blob);
121
- last.bytes += size;
122
- } else chunks.push({ blobs: [blob], bytes: size });
123
- }
124
- for (const { blobs: chunk, bytes } of chunks) {
125
- // Each blob's header and trailing newline on top of its bytes.
126
- const out = gitBytes(
127
- ["cat-file", "--batch"],
128
- chunk.join("\n") + "\n",
129
- bytes + chunk.length * 128 + 1024,
130
- );
131
- if (out === null) return false;
132
- let at = 0;
133
- for (const blob of chunk) {
134
- const nl = out.indexOf(10, at);
135
- if (nl < 0) return false;
136
- const m = /^([0-9a-f]+) blob (\d+)$/.exec(out.subarray(at, nl).toString("utf8"));
137
- if (!m || m[1] !== blob) return false;
138
- const size = Number(m[2]);
139
- const body = out.subarray(nl + 1, nl + 1 + size);
140
- if (body.length !== size) return false;
141
- at = nl + 1 + size + 1;
142
- for (const rel of pathsOf.get(blob)!) {
143
- const full = normalize(join(root, rel));
144
- const back = relative(root, full);
145
- if (isAbsolute(rel) || !inRoot(back)) continue;
146
- mkdirSync(dirname(full), { recursive: true });
147
- writeFileSync(full, body);
148
- }
149
- }
150
- }
151
- return true;
152
- };
153
- const treesDir = join(stateRoot, opts.treesDir ?? SCAN_TREES_DIR);
154
- const list = (sha: string): TreeFile[] | null => {
155
- if (!SHA_RE.test(sha)) return null;
156
- const listed = git(["ls-tree", "-r", "-z", "--full-tree", sha], 512 * 1024 * 1024);
157
- if (listed === null) return null;
158
- const files: TreeFile[] = [];
159
- for (const rec of listed.split("\0")) {
160
- // `<mode> <type> <object>\t<path>` — regular files only: no submodule
161
- // (`commit`), no symlink (120000) a lens could follow out of the tree.
162
- const m = /^(\d{6}) (\w+) ([0-9a-f]+)\t(.+)$/s.exec(rec);
163
- if (!m || m[2] !== "blob" || m[1] === "120000") continue;
164
- files.push({ path: m[4]!, blob: m[3]! });
165
- }
166
- return files;
167
- };
168
- return {
169
- location: repoRoot,
170
- resolve: (ref) => {
171
- if (!/^[A-Za-z0-9._/@^~-]+$/.test(ref) || ref.startsWith("-")) return null;
172
- const sha = git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`])?.trim() ?? "";
173
- return SHA_RE.test(sha) ? sha : null;
174
- },
175
- open: (sha, keep) => {
176
- if (!SHA_RE.test(sha))
177
- return { ok: false, reason: `not a commit sha: ${JSON.stringify(sha)}` };
178
- lastFailure = "";
179
- const files = list(sha);
180
- if (files === null)
181
- return { ok: false, reason: `git cannot list that commit's tree${because()}` };
182
- const root = join(treesDir, sha);
183
- const marker = `${root}${COMPLETE_SUFFIX}`;
184
- try {
185
- mkdirSync(treesDir, { recursive: true });
186
- for (const other of readdirSync(treesDir)) {
187
- const owner = other.endsWith(COMPLETE_SUFFIX)
188
- ? other.slice(0, -COMPLETE_SUFFIX.length)
189
- : other;
190
- if (owner !== sha && !keep.includes(owner))
191
- rmSync(join(treesDir, other), { recursive: true, force: true });
192
- }
193
- if (!existsSync(marker)) {
194
- rmSync(root, { recursive: true, force: true });
195
- mkdirSync(root, { recursive: true });
196
- if (!materialise(root, files))
197
- return { ok: false, reason: `git cannot read that commit's blobs${because()}` };
198
- writeFileSync(marker, `${sha}\n`);
199
- }
200
- } catch (err) {
201
- return {
202
- ok: false,
203
- reason: `cannot materialise the tree: ${err instanceof Error ? err.message : String(err)}`,
204
- };
205
- }
206
- return { ok: true, tree: treeAt(sha, root, files) };
207
- },
208
- list,
209
- };
210
- }
211
-
212
- /** A materialised tree as the runner reads it: text within the reader's
213
- * size cap, never a binary (`isBinaryContent` — one stray NUL in a source
214
- * file is still text), never a path that leaves the root. What it cannot
215
- * read it says why (`whyUnreadable`), so the plan REPORTS a skipped binary
216
- * rather than dropping it silently. */
217
- export function treeAt(sha: string, root: string, files: TreeFile[]): ScanTree {
218
- const why = new Map<string, UnreadableReason>();
219
- const read = (rel: string): string | null => {
220
- if (isAbsolute(rel)) return null;
221
- const full = normalize(join(root, rel));
222
- const back = relative(root, full);
223
- if (!inRoot(back)) return null;
224
- try {
225
- const st = statSync(full);
226
- if (!st.isFile()) return null;
227
- if (st.size > MAX_READ_BYTES) {
228
- why.set(rel, "too-big");
229
- return null;
230
- }
231
- const buf = readFileSync(full);
232
- if (isBinaryContent(buf, rel)) {
233
- why.set(rel, "binary");
234
- return null;
235
- }
236
- return buf.toString("utf8");
237
- } catch {
238
- return null;
239
- }
240
- };
241
- const cache = new Map<string, number | null>();
242
- return {
243
- sha,
244
- root,
245
- files,
246
- read,
247
- whyUnreadable: (rel) => {
248
- if (read(rel) !== null) return null;
249
- return why.get(rel) ?? "unreadable";
250
- },
251
- lines: (rel) => {
252
- if (cache.has(rel)) return cache.get(rel)!;
253
- const text = read(rel);
254
- const n = text === null ? null : textLineCount(text);
255
- cache.set(rel, n);
256
- return n;
257
- },
258
- };
259
- }
1
+ // Moved to the plugin (packages/pi-plugin/src/scan/scantree.ts), the scan engine's
2
+ // home: this name stays for the app's importers.
3
+ export * from "@quickchatai/pi-quickreview/src/scan/scantree.js";
@@ -1,347 +1,3 @@
1
- // Scan stage 2's start channel: the files through which a stage-2 proposal
2
- // is shown and started from outside the companion — the TUI's click. A leaf:
3
- // it reaches its siblings by type only, so the TUI (which never imports the
4
- // setup engine — tests/single-shepherd.test.ts) can read the proposal and
5
- // ask for its start. The click's whole TUI half lives here
6
- // (`clickStartStage2`, `stage2ClickOutcome`); the [8] scan tab's plan pane
7
- // (`P`, then ⏎) is its button.
8
- //
9
- // Stage 2 starts one of two ways, and neither keeps an approval record:
10
- //
11
- // * the operator's word — the companion calls `scan_stage2_start` with the
12
- // proposal's key (and, for a narrowed "do it", the `rows` it may run —
13
- // the same `keptRows` rule as the click's narrowing below);
14
- // * a TUI click — the TUI writes `STAGE2_START_FILE` naming the key of the
15
- // proposal it showed; the companion's scan belt picks it up within
16
- // seconds and starts exactly that proposal, answering in
17
- // `STAGE2_RESULT_FILE`.
18
- //
19
- // Either way the start quotes a key, and the engine re-plans before it runs:
20
- // a proposal that moved since it was shown (a new proposal, a new commit, a
21
- // price that changed) is refused, so nothing runs that the operator did not
22
- // see priced. A request is answered once — the result names the request it
23
- // answered — so a click is never replayed.
24
- //
25
- // The click may narrow the plan (space toggles a row off in the plan pane;
26
- // the operator's word narrows the same way through `scan_stage2_start`'s
27
- // `rows`): the request then names the runnable rows the operator kept, and
28
- // the run is exactly those rows, stopped hard at their total — the sum
29
- // the pane showed. It can only narrow: a row that was not runnable in the
30
- // proposal (covered, over the budget) can never be named, so nothing runs
31
- // that was not shown priced and runnable.
32
- //
33
- // Files, all in the state home: `scan/stage2.json` (the latest proposal, the
34
- // engine's), `scan/stage2-start.json` (the TUI's request),
35
- // `scan/stage2-start-result.json` (the engine's answer).
36
- import { join } from "node:path";
37
- import { readTextIfPresent, writeAtomic } from "../channelfs.js";
38
- import type { ScanDepth } from "../config.js";
39
- import type { RowPrice, Stage2Floor, Stage2Row, Stage2RowRequest } from "./stage2.js";
40
-
41
- export const STAGE2_FILE = "scan/stage2.json";
42
- export const STAGE2_START_FILE = "scan/stage2-start.json";
43
- export const STAGE2_RESULT_FILE = "scan/stage2-start-result.json";
44
- export const STAGE2_VERSION = 1;
45
-
46
- /** The latest proposal, as stored — what the TUI shows and a start quotes. */
47
- export interface StoredStage2Proposal {
48
- key: string;
49
- sha: string;
50
- at: string;
51
- /** What the companion asked for — the start re-plans from exactly this. */
52
- request: Stage2RowRequest[];
53
- budget_usd: number | null;
54
- /** The plan it priced. */
55
- rows: Stage2Row[];
56
- /** The hard stop: the runnable rows' total. */
57
- cap_usd: number;
58
- runnable: number;
59
- over_budget_usd: number;
60
- validator: boolean;
61
- /** The depth it was made at — a start re-plans at this depth, whatever
62
- * `[scan] depth` says by then. Absent = standard (a proposal stored before
63
- * depths). */
64
- depth?: ScanDepth;
65
- /** A deep proposal's floor: its lenses, and what it left out. */
66
- floor?: Stage2Floor | null;
67
- }
68
-
69
- /** The depth a stored proposal was made at. */
70
- export const proposalDepth = (p: Pick<StoredStage2Proposal, "depth">): ScanDepth =>
71
- p.depth === "deep" ? "deep" : "standard";
72
-
73
- export interface Stage2File {
74
- version: number;
75
- proposal: StoredStage2Proposal | null;
76
- }
77
-
78
- export interface Stage2StartRequest {
79
- /** Unique per click; the result names it. */
80
- id: string;
81
- key: string;
82
- at: string;
83
- by: "tui";
84
- /** The runnable rows the operator kept (their ids); absent = all of them. */
85
- rows?: string[];
86
- }
87
-
88
- export interface Stage2StartResult {
89
- request: string;
90
- key: string;
91
- at: string;
92
- ok: boolean;
93
- /** The run it started. */
94
- run: string | null;
95
- errors: string[];
96
- }
97
-
98
- const clean = (err: unknown): string => (err instanceof Error ? err.message : String(err));
99
-
100
- /** Parse the proposal file; throws on anything this version does not read. */
101
- export function parseStage2File(text: string): Stage2File {
102
- const raw = JSON.parse(text) as Partial<Stage2File> | null;
103
- if (typeof raw !== "object" || raw === null || raw.version !== STAGE2_VERSION)
104
- throw new Error("not a version-1 stage 2 file");
105
- const p = raw.proposal ?? null;
106
- if (
107
- p !== null &&
108
- (typeof p !== "object" ||
109
- typeof p.key !== "string" ||
110
- typeof p.sha !== "string" ||
111
- typeof p.cap_usd !== "number" ||
112
- !Array.isArray(p.rows) ||
113
- !Array.isArray(p.request))
114
- ) {
115
- throw new Error("`proposal` is not a stage 2 proposal");
116
- }
117
- return { version: STAGE2_VERSION, proposal: p };
118
- }
119
-
120
- /** Parse a start request, or null when it is not one. */
121
- export function parseStartRequest(text: string): Stage2StartRequest | null {
122
- try {
123
- const r = JSON.parse(text) as Partial<Stage2StartRequest> | null;
124
- if (
125
- typeof r !== "object" ||
126
- r === null ||
127
- typeof r.id !== "string" ||
128
- r.id === "" ||
129
- typeof r.key !== "string"
130
- )
131
- return null;
132
- if (
133
- r.rows !== undefined &&
134
- (!Array.isArray(r.rows) || !r.rows.every((x) => typeof x === "string"))
135
- )
136
- return null;
137
- return {
138
- id: r.id,
139
- key: r.key,
140
- at: typeof r.at === "string" ? r.at : "",
141
- by: "tui",
142
- ...(r.rows !== undefined ? { rows: [...r.rows] } : {}),
143
- };
144
- } catch {
145
- return null;
146
- }
147
- }
148
-
149
- /** Parse a start result, or null when it is not one. */
150
- export function parseStartResult(text: string): Stage2StartResult | null {
151
- try {
152
- const r = JSON.parse(text) as Partial<Stage2StartResult> | null;
153
- if (
154
- typeof r !== "object" ||
155
- r === null ||
156
- typeof r.request !== "string" ||
157
- typeof r.ok !== "boolean"
158
- )
159
- return null;
160
- return {
161
- request: r.request,
162
- key: typeof r.key === "string" ? r.key : "",
163
- at: typeof r.at === "string" ? r.at : "",
164
- ok: r.ok,
165
- run: typeof r.run === "string" ? r.run : null,
166
- errors: Array.isArray(r.errors)
167
- ? r.errors.filter((e): e is string => typeof e === "string")
168
- : [],
169
- };
170
- } catch {
171
- return null;
172
- }
173
- }
174
-
175
- /** The latest stage-2 proposal in the state home at `home`, none, or why it
176
- * cannot be read (then the TUI shows UNKNOWN — never "no proposal"). */
177
- export function readStage2Proposal(
178
- home: string,
179
- ): { ok: true; proposal: StoredStage2Proposal | null } | { ok: false; error: string } {
180
- try {
181
- const text = readTextIfPresent(join(home, STAGE2_FILE));
182
- return { ok: true, proposal: text === null ? null : parseStage2File(text).proposal };
183
- } catch (err) {
184
- return { ok: false, error: `${STAGE2_FILE} cannot be read: ${clean(err)}` };
185
- }
186
- }
187
-
188
- /**
189
- * The runnable rows a start runs: all of them, or the subset `keep` names —
190
- * refused when `keep` is empty or names a row that is not runnable in the
191
- * proposal (the engine applies the same rule after it re-plans).
192
- */
193
- export function keptRows(
194
- rows: readonly Stage2Row[],
195
- keep: readonly string[] | undefined,
196
- ): { ok: true; rows: Stage2Row[]; cap_usd: number | null } | { ok: false; errors: string[] } {
197
- const runnable = rows.filter((r) => r.status === "run");
198
- if (keep === undefined) return { ok: true, rows: runnable, cap_usd: null };
199
- if (keep.length === 0)
200
- return { ok: false, errors: ["every row is toggled off — keep at least one"] };
201
- const bad = keep.filter((id) => !runnable.some((r) => r.id === id));
202
- if (bad.length > 0)
203
- return { ok: false, errors: [`not a runnable row of the proposal: ${bad.join(", ")}`] };
204
- const kept = runnable.filter((r) => keep.includes(r.id));
205
- return { ok: true, rows: kept, cap_usd: kept.reduce((n, r) => n + r.estimate_usd, 0) };
206
- }
207
-
208
- /**
209
- * The TUI's click: ask the companion to start the stored proposal whose key
210
- * the TUI showed — all its runnable rows, or the ones in `keep`. Refused when
211
- * there is no proposal, it is no longer the one shown (`key`), or `keep`
212
- * names a row that is not runnable. Writes the request; the companion starts
213
- * it (or says why not) in `STAGE2_RESULT_FILE`.
214
- */
215
- export function requestStage2Start(
216
- home: string,
217
- key: string,
218
- now: Date,
219
- keep?: readonly string[],
220
- ):
221
- | {
222
- ok: true;
223
- request: Stage2StartRequest;
224
- proposal: StoredStage2Proposal;
225
- rows: number;
226
- cap_usd: number;
227
- }
228
- | { ok: false; errors: string[] } {
229
- const read = readStage2Proposal(home);
230
- if (!read.ok) return { ok: false, errors: [read.error] };
231
- const p = read.proposal;
232
- if (p === null)
233
- return { ok: false, errors: ["no stage 2 proposal — ask the companion to propose one"] };
234
- if (p.key !== key) {
235
- return {
236
- ok: false,
237
- errors: [
238
- `the proposal changed since it was shown (showed ${key}, now ${p.key}) — look at the new one first`,
239
- ],
240
- };
241
- }
242
- if (p.runnable === 0) return { ok: false, errors: ["the proposal has no row to run"] };
243
- const kept = keptRows(p.rows, keep);
244
- if (!kept.ok) return kept;
245
- const request: Stage2StartRequest = {
246
- id: `${now.toISOString()}-${Math.random().toString(36).slice(2, 8)}`,
247
- key,
248
- at: now.toISOString(),
249
- by: "tui",
250
- ...(keep !== undefined ? { rows: [...keep] } : {}),
251
- };
252
- try {
253
- writeAtomic(join(home, STAGE2_START_FILE), `${JSON.stringify(request)}\n`);
254
- } catch (err) {
255
- return { ok: false, errors: [`the start request could not be written: ${clean(err)}`] };
256
- }
257
- return {
258
- ok: true,
259
- request,
260
- proposal: p,
261
- rows: kept.rows.length,
262
- cap_usd: kept.cap_usd ?? p.cap_usd,
263
- };
264
- }
265
-
266
- /** The companion's answer to the request `id`, or null while it has not
267
- * answered (or answered another one); throws when the answer file exists
268
- * but cannot be read. */
269
- export function readStage2StartResult(home: string, id: string): Stage2StartResult | null {
270
- const text = readTextIfPresent(join(home, STAGE2_RESULT_FILE));
271
- const r = text === null ? null : parseStartResult(text);
272
- return r !== null && r.request === id ? r : null;
273
- }
274
-
275
- // ---- the TUI's click -------------------------------------------------------------
276
-
277
- const usd = (n: number): string => `$${n.toFixed(2)}`;
278
-
279
- /**
280
- * The click: ask the companion to start the proposal whose key the TUI
281
- * showed — its kept rows when the operator toggled some off. The notice line
282
- * says what was asked, or why nothing was.
283
- */
284
- export function clickStartStage2(
285
- home: string,
286
- key: string,
287
- now: Date = new Date(),
288
- keep?: readonly string[],
289
- ): { ok: true; request: string; line: string } | { ok: false; line: string } {
290
- const r = requestStage2Start(home, key, now, keep);
291
- if (!r.ok) return { ok: false, line: `stage 2 not started: ${r.errors.join("; ")}` };
292
- return {
293
- ok: true,
294
- request: r.request.id,
295
- line: `stage 2 asked to start — ${r.rows} row(s), hard stop ${usd(r.cap_usd)}; the companion starts it within seconds`,
296
- };
297
- }
298
-
299
- /** What became of the click `request`: the companion's answer, or null while
300
- * it has not answered (a companion that is down starts nothing) — or that
301
- * the answer cannot be read. */
302
- export function stage2ClickOutcome(home: string, request: string): string | null {
303
- let r: Stage2StartResult | null;
304
- try {
305
- r = readStage2StartResult(home, request);
306
- } catch (err) {
307
- // Not "no answer yet": the answer may be there, unreadable — said once,
308
- // never polled for silently.
309
- return `stage 2's answer cannot be read (${STAGE2_RESULT_FILE}: ${clean(err)}) — check scan_status`;
310
- }
311
- if (r === null) return null;
312
- return r.ok ? `stage 2 started: ${r.run}` : `stage 2 refused: ${r.errors.join("; ")}`;
313
- }
314
-
315
- // ---- a row's price, as the preview names it -------------------------------------
316
-
317
- /** Whether a row's price is a measurement of the row's own lens at its own
318
- * settings and depth — every other basis is an estimate. */
319
- export function priceIsMeasured(p: RowPrice): boolean {
320
- return p.basis === "measured" && p.across_depth !== true;
321
- }
322
-
323
- /** What the preview says a row's price stands on: `measured`, or
324
- * `estimate: …` and how it was made. */
325
- export function priceLabel(p: RowPrice): string {
326
- const label = basisLabel(p);
327
- return p.across_depth
328
- ? `${p.basis === "measured" ? "estimate: measured" : label} — from readings at the other depth`
329
- : label;
330
- }
331
-
332
- function basisLabel(p: RowPrice): string {
333
- const onOff = (v: boolean): string => (v ? "on" : "off");
334
- switch (p.basis) {
335
- case "measured":
336
- return "measured";
337
- case "borrowed":
338
- return `estimate: borrowed from ${p.from}`;
339
- case "learned":
340
- return p.learned
341
- ? `estimate: learned from ${p.from} with the validator ${onOff(p.learned.validator)} (×${p.learned.factor.toFixed(2)} stage 1), ` +
342
- `stage 1 validator ${onOff(!p.learned.validator)}/${onOff(p.learned.validator)} ×${p.learned.skeptic.toFixed(2)}`
343
- : `estimate: learned from ${p.from}`;
344
- default:
345
- return `estimate: extrapolated from ${p.from}${p.factor !== undefined ? ` (×${p.factor})` : ""}`;
346
- }
347
- }
1
+ // Moved to the plugin (packages/pi-plugin/src/scan/stage2request.ts), the scan engine's
2
+ // home: this name stays for the app's importers.
3
+ export * from "@quickchatai/pi-quickreview/src/scan/stage2request.js";