@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,4 +1,3 @@
1
- import { usd, findingLocation } from "../src/setup/presentation.js";
2
1
  // setup-scan — the scan half of the companion's setup tool belt. Not an
3
2
  // extension of its own: extensions/setup.ts (the one setup & scan extension)
4
3
  // registers these beside the setup_* tools. The companion judges
@@ -34,538 +33,21 @@ import { usd, findingLocation } from "../src/setup/presentation.js";
34
33
  //
35
34
  // Convention (setup.ts, onboarding.ts): every tool answers a refusal as its
36
35
  // result, never a throw.
37
- import { FINDING_STATES } from "../src/setup/findings.js";
38
- import { join } from "node:path";
39
36
  import { textResult as text, refusedResult as refused, } from "@quickchatai/pi-quickreview/extensions/pi-tool.js";
40
37
  import { sandboxRoot } from "../src/config.js";
41
38
  import { homeProfileLookup, stateHome } from "../src/install.js";
42
39
  import { scanCasesForEnv } from "@quickchatai/pi-quickreview/src/engine/auth.js";
43
40
  import { piCaseRunner } from "../src/setup/caserunner.js";
44
- import { VALIDITY_ORDER, clickStopScan, readStopResult, scanStopOutcome, validityOf, } from "../src/setup/scanreport.js";
45
- import { FindingsStore, dismissedLookalike, lookalikeRank } from "../src/setup/findings.js";
46
41
  import { stateHomeFs } from "../src/setup/adapters.js";
47
- import { SEVERITIES } from "../src/setup/scan.js";
48
- import { lensTiers, stage2Lenses, MAX_STAGE2_ROWS } from "../src/setup/stage2.js";
49
- import { priceIsMeasured, priceLabel, } from "../src/setup/stage2request.js";
50
- import { buildScanAgents, loadScanConfig, scanSettings, SCAN_AGENTS_DIR, } from "../src/setup/scanbuild.js";
51
- import { escapeControls } from "../src/setup/textsniff.js";
52
- import { SCAN_TRANSCRIPTS_DIR, ScanRunner, } from "../src/setup/scanrun.js";
53
- import { gitScanSource } from "../src/setup/scantree.js";
42
+ import { loadScanConfig } from "../src/setup/scanbuild.js";
43
+ import { assembleScan, scanRuns } from "@quickchatai/pi-quickreview/src/scan/scanassemble.js";
44
+ import { FINDINGS_LIMIT, FINDINGS_LIMIT_MAX, answerMatch, findingsFilter, listFindings, previewDetails, proposalDetails, renderFindings, renderPreview, renderProposal, renderQuestion, renderStatus, renderSuggestions, stage2StartedLine, startedDetails, startedLine, stopScan, } from "@quickchatai/pi-quickreview/src/scan/scanrender.js";
54
45
  import { ScanTriage } from "../src/setup/triage.js";
55
- import { fileLease } from "../src/setup/scanlease.js";
56
46
  const NO_PARAMS = { type: "object", properties: {} };
57
- export const TOOL_VERBS = {
58
- start: (p) => `scan_start with key ${p.key} and sha ${p.sha}${p.mode === "delta" ? " and delta true" : ""}`,
59
- matches: "scan_matches",
60
- match: (key) => `scan_match: key ${key} and the pairs`,
61
- stage2Start: (key) => `scan_stage2_start with key ${key} — only after the operator agreed to the cost ("do it"); the TUI's start click starts the same proposal. ` +
62
- `To run only some rows: add rows (their ids, e.g. ["row-1"]) — only runnable rows, and the run stops hard at their total.`,
63
- status: "scan_status",
64
- stop: "scan_stop",
65
- };
66
- export const CLI_VERBS = {
67
- start: (p) => `qcr scan start --key ${p.key} --sha ${p.sha}${p.mode === "delta" ? " --delta" : ""}`,
68
- matches: "qcr scan matches",
69
- match: (key) => `qcr scan match --key ${key} OLD=NEW… — the pairs`,
70
- stage2Start: (key) => `qcr scan stage2 start --key ${key} — only after the operator agreed to the cost; the TUI's start click starts the same proposal. ` +
71
- "To run only some rows: add --rows with their ids (e.g. --rows row-1,row-3) — only runnable rows, and the run stops hard at their total.",
72
- status: "qcr scan status",
73
- stop: "qcr scan stop",
74
- };
75
- /** A delta's unchanged-but-owed files: those no reading covers, and — on a
76
- * deep delta — those a standard reading covers but no deep one does yet. */
77
- function owedClause(d) {
78
- if (d === null || d.owed <= 0)
79
- return "";
80
- const shallow = Math.min(d.owed_shallow ?? 0, d.owed);
81
- const unread = d.owed - shallow;
82
- const parts = [];
83
- if (unread > 0)
84
- parts.push(`${unread} of the files are unchanged but no reading covers them yet (never read, cut short or unreadable before)`);
85
- if (shallow > 0)
86
- parts.push(`${shallow} of the files are unchanged but not yet read at deep depth`);
87
- return `; ${parts.join("; ")} — read first`;
88
- }
89
- export function renderPreview(p, verbs = TOOL_VERBS) {
90
- const out = [];
91
- const deep = p.depth === "deep";
92
- const head = `${deep ? "(deep) " : ""}at ${p.sha.slice(0, 12)} — ${p.settings.tier} at ${p.settings.effort} effort, skeptic ${p.settings.validator ? `on (at the lens's tier, ${p.settings.validate_tier}; its cost is in the price)` : "off (findings stay unjudged — validity unknown)"}`;
93
- // A rate measured at the other depth stands in until this depth is priced.
94
- const across = p.rate_across_depth
95
- ? ` — from readings at the other depth: nothing at ${p.depth} depth is priced at these settings yet`
96
- : "";
97
- if (p.mode === "delta") {
98
- out.push(`DELTA re-scan ${head}, against the last scan at ${p.delta?.since.slice(0, 12) ?? "?"}.`, `Files whose content changed since, and any not yet read whole: ${p.files} files, ${p.lines} lines in ${p.cases.length} case(s) — ` +
99
- `${p.covered} already read at this content` +
100
- owedClause(p.delta) +
101
- `. Estimate ${usd(p.estimate_usd)}, measured at ` +
102
- `${usd(p.rate?.usd_per_kline ?? null)}/kline over ${p.rate?.cases ?? 0} finished case(s)${across}. The run stops hard at the estimate. ` +
103
- "No deep pass runs on a delta.");
104
- }
105
- else if (p.mode === "calibration") {
106
- out.push(`CALIBRATION run ${head}.`, `No measured price for stage 1 at these settings yet (${p.calibration.measured} of ${p.calibration.wanted} priced cases). ` +
107
- `This run reads ${p.cases.length} case(s) from units spread across the size range and measures what they cost; ` +
108
- `it stops hard at ${usd(p.cap_usd)}. Its findings are real stage-1 findings. Preview again afterwards for the full estimate` +
109
- (p.remaining_cases > 0 ? ` (${p.remaining_cases} more case(s) after it).` : "."));
110
- }
111
- else {
112
- out.push(`STAGE 1 ${head}.`, `Estimate ${usd(p.estimate_usd)} for ${p.cases.length} case(s), ${p.files} files, ${p.lines} lines — measured at ` +
113
- `${usd(p.rate?.usd_per_kline ?? null)}/kline over ${p.rate?.cases ?? 0} finished case(s)${across}. The run stops hard at the estimate.`);
114
- }
115
- if (deep) {
116
- out.push("DEEP: pass-scan names a deep lens for every focus file, and a file the ledger holds only a standard reading of is read again. " +
117
- "A deep stage 2 proposal then adds the floor — the [scan] deep_lenses over every file this stage reads.");
118
- }
119
- const rows = p.cases
120
- .slice(0, 40)
121
- .map((c) => ` - ${c.id}: ${c.files.length} files, ${c.lines} lines${c.estimate_usd !== null ? `, ~${usd(c.estimate_usd)}` : ""}`);
122
- if (p.cases.length > 40)
123
- rows.push(` … and ${p.cases.length - 40} more`);
124
- out.push(rows.join("\n"));
125
- const skipped = Object.entries(p.excluded)
126
- .filter(([, n]) => n > 0)
127
- .map(([why, n]) => `${n} ${why}`);
128
- out.push(`Not read: ${skipped.length ? skipped.join(", ") : "nothing excluded"}; ${p.covered} already in the ledger at this content; ` +
129
- `${p.unreadable} unreadable${p.settings.exclude.length ? `; exclude globs ${p.settings.exclude.join(", ")}` : ""}.`);
130
- // What the tree lists but no lens can read, by reason and by name — a
131
- // binary is REPORTED as skipped, never silently absent from coverage.
132
- const skippedBy = new Map();
133
- for (const f of p.skipped ?? [])
134
- skippedBy.set(f.why, [...(skippedBy.get(f.why) ?? []), f.path]);
135
- for (const [why, paths] of skippedBy) {
136
- const label = why === "binary" ? "binary" : why === "too-big" ? "over the size cap" : "unreadable";
137
- out.push(`skipped: ${label} — ${paths.length} file(s): ${paths.slice(0, 5).map(escapeControls).join(", ")}${paths.length > 5 ? ", …" : ""}; not read by any lens.`);
138
- }
139
- if (p.unassigned.length > 0) {
140
- out.push(`! ${p.unassigned.length} file(s) no unit owns at this sha (e.g. ${p.unassigned.slice(0, 3).join(", ")}) — the unit cut may need adjusting; they are not read.`);
141
- }
142
- if (p.reconcile_only) {
143
- out.push(`Nothing to read, but ${p.reconcile_only}: the run reads no case and only reconciles the findings (no cost).`);
144
- }
145
- for (const b of p.blockers)
146
- out.push(`✗ ${b}`);
147
- if (p.blockers.length === 0) {
148
- out.push(`To run it: ${verbs.start(p)} — only after the operator agreed to the cost.`);
149
- }
150
- return out.join("\n\n");
151
- }
152
- /** A run's findings by validity tier — `(2 confirmed, 1 likely, 3 doubtful)`
153
- * — from the per-case counts; `unknown` for what no skeptic judged (the
154
- * validator off or failed, or a case recorded before the tiers). */
155
- function tierCounts(r, findings, doubted) {
156
- if (!r.settings.validator)
157
- return doubted ? ` (${doubted} may not be valid)` : " (skeptic off — validity unknown)";
158
- const confirmed = r.cases.reduce((n, c) => n + (c.confirmed ?? 0), 0);
159
- const likely = r.cases.reduce((n, c) => n + (c.likely ?? 0), 0);
160
- const unknown = findings - confirmed - likely - doubted;
161
- if (findings === 0)
162
- return "";
163
- return ` (${confirmed} confirmed, ${likely} likely, ${doubted} doubtful${unknown > 0 ? `, ${unknown} unknown` : ""})`;
164
- }
165
- export function renderStatus(s, verbs = TOOL_VERBS) {
166
- const out = [];
167
- for (const p of s.problems)
168
- out.push(`✗ ${p}`);
169
- const r = s.run;
170
- if (r === null) {
171
- out.push(s.problems.length > 0 ? "Latest scan: UNKNOWN — see above." : "No scan has run yet.");
172
- }
173
- else {
174
- const done = r.cases.filter((c) => c.status === "done").length;
175
- const findings = r.cases.reduce((n, c) => n + c.findings, 0);
176
- const doubted = r.cases.reduce((n, c) => n + c.doubted, 0);
177
- out.push(`${r.id} (${r.mode === "stage2" ? "stage 2" : r.mode}${r.settings.depth === "deep" ? ", deep" : ""}) at ${r.sha.slice(0, 12)}: ${r.display.toUpperCase()} — ${done}/${r.cases.length} cases, ` +
178
- `${findings} finding(s)${tierCounts(r, findings, doubted)}, spent ${usd(r.spent_usd)} of ${usd(r.cap_usd)}` +
179
- (r.unpriced_cases
180
- ? ` (${r.unpriced_cases} unpriced case(s) counted at their estimate)`
181
- : ""));
182
- // Stage 2 is re-entered by a new proposal (the rows already read are
183
- // covered, free); stage 1 by a new preview.
184
- const again = r.mode === "stage2" ? "Propose stage 2 again" : "Preview";
185
- if (r.display === "interrupted")
186
- out.push(`It was interrupted (the process driving it went away — the companion restarted, or a \`qcr scan\` driver died). ${again} and start again — the ledger resumes it.`);
187
- if (r.display === "capped")
188
- out.push(`It stopped at its hard cap. ${again} for the price of what is left.`);
189
- if (r.mode === "stage2") {
190
- for (const c of r.cases.slice(0, 40)) {
191
- out.push(` - ${c.id} ${c.lens ?? "?"} at ${c.tier ?? "?"} (${(c.units ?? [c.unit]).join(", ")}): ${c.status}, ${c.findings} finding(s), ${usd(c.cost_usd)}` +
192
- (c.reads !== undefined ? `, ${c.reads} read(s)` : "") +
193
- (c.unopened?.length ? `, ${c.unopened.length} focus file(s) not opened` : ""));
194
- }
195
- }
196
- // Where to read back what a lens did, when the runner kept it.
197
- if (r.cases.some((c) => c.transcript !== undefined))
198
- out.push(`Transcripts: ${SCAN_TRANSCRIPTS_DIR}/${r.id}/ under the state home (one .jsonl per case, .validate.jsonl for its skeptic).`);
199
- const failed = r.cases.filter((c) => c.status === "failed");
200
- for (const c of failed.slice(0, 10))
201
- out.push(` ✗ ${c.id}: ${c.error}`);
202
- for (const p of r.problems.slice(-5))
203
- out.push(` ! ${p}`);
204
- if (r.tickets_closed)
205
- out.push(`Tickets closed as fixed at ${r.sha.slice(0, 12)}: ${r.tickets_closed}.`);
206
- if (r.matching && (r.matching.fixed > 0 || r.matching.pending > 0)) {
207
- out.push(`Findings no longer reported: ${r.matching.fixed} fixed` +
208
- (r.matching.pending > 0
209
- ? `, ${r.matching.pending} waiting on your match (${verbs.matches})`
210
- : ""));
211
- }
212
- // A dismissal is sticky: what would have been fixed is only stamped.
213
- const dismissedGone = r.matching?.dismissed_unreported ?? 0;
214
- if (dismissedGone > 0) {
215
- out.push(`Dismissed and no longer reported: ${dismissedGone} — kept dismissed (a scan never overwrites the operator's triage), stamped for audit.`);
216
- }
217
- // A record written before the stamp existed has no count: nothing to say.
218
- const notReproduced = r.matching?.not_reproduced ?? 0;
219
- if (notReproduced > 0) {
220
- out.push(`Not reproduced: ${notReproduced} open finding(s) re-read at UNCHANGED content without being reported again — ` +
221
- "kept open (only changed code is fixed); dismiss any that were noise.");
222
- }
223
- }
224
- if (s.ledger) {
225
- out.push(`Ledger: ${s.ledger.files} file(s) read by stage 1` +
226
- (s.ledger.deep_files ? `, ${s.ledger.deep_files} file × lens read by stage 2` : "") +
227
- `, ${usd(s.ledger.cost_usd)} recorded.`);
228
- }
229
- return out.join("\n");
230
- }
231
- /** Findings in validity order — confirmed, likely, doubtful, unknown — the
232
- * store's order (most severe first) kept inside each tier, a finding that
233
- * looks like a dismissed one below the rest of its tier. */
234
- export function byValidity(fs) {
235
- return VALIDITY_ORDER.flatMap((v) => {
236
- const tier = fs.filter((f) => validityOf(f) === v);
237
- return [
238
- ...tier.filter((f) => lookalikeRank(f) === 0),
239
- ...tier.filter((f) => lookalikeRank(f) === 1),
240
- ];
241
- });
242
- }
243
- /** A new finding that resembles one the operator dismissed: which, and why
244
- * — flagged for triage, never dismissed by the scan. */
245
- function lookalikeNote(f) {
246
- const d = dismissedLookalike(f);
247
- return d
248
- ? `, LOOKS LIKE DISMISSED ${d.fingerprint.slice(0, 12)}: ${d.reason} — not auto-dismissed`
249
- : "";
250
- }
251
- function validityNote(f) {
252
- const v = validityOf(f);
253
- if (v === "doubtful")
254
- return `, DOUBTFUL — ${f.doubt?.word ?? "?"}${f.doubt?.class ? ` [${f.doubt.class}]` : ""}`;
255
- if (v === "unknown")
256
- return f.doubt
257
- ? `, MAY NOT BE VALID — ${f.doubt.word}`
258
- : f.validated
259
- ? ", validity unknown"
260
- : ", unvalidated";
261
- return `, ${v.toUpperCase()}`;
262
- }
263
- export function renderFindings(fs, total) {
264
- if (fs.length === 0)
265
- return "No stored scan findings match.";
266
- const rows = fs.map((f) => `- [${f.severity}] ${findingLocation(f)} (${f.unit}, ${f.kind}, ${f.state}` +
267
- validityNote(f) +
268
- `${f.capped_from ? `, ${f.capped_from}→${f.severity}: ${f.reachability}` : ""}` +
269
- `${f.anchor_quoted === false ? ", ANCHOR NOT QUOTED — the lines may not be the defect's" : ""}` +
270
- `${f.fixed ? ` at ${f.fixed.sha.slice(0, 12)}, was ${f.fixed.from}` : ""}` +
271
- `${f.state === "dismissed" && f.unreported ? `, no longer reported at ${f.unreported.sha.slice(0, 12)} — kept dismissed` : ""}` +
272
- `${f.not_reproduced ? `, NOT REPRODUCED by ${f.not_reproduced.count} re-read(s) of unchanged code, latest ${f.not_reproduced.run}` : ""}` +
273
- `${lookalikeNote(f)}) ` +
274
- `${f.claim} [${f.fingerprint.slice(0, 12)}]`);
275
- if (total > fs.length)
276
- rows.push(`… ${total - fs.length} more — narrow the filter.`);
277
- return rows.join("\n");
278
- }
279
- export function renderQuestion(q, verbs = TOOL_VERBS) {
280
- if (q === null)
281
- return "No match question is pending: every finding a re-scan stopped reporting has been settled.";
282
- const row = (f) => ` - ${f.fingerprint} [${f.severity}] ${findingLocation(f)} (${f.unit}, ${f.lens}, ${f.kind}` +
283
- `${f.state === "new" ? "" : `, ${f.state}`}${lookalikeNote(f)}) ${f.claim}`;
284
- return [
285
- `MATCH QUESTION ${q.key} (re-scan ${q.run} at ${q.sha.slice(0, 12)}).`,
286
- `No longer reported exactly (${q.unreported.length}):`,
287
- ...q.unreported.map(row),
288
- `Fresh — first seen by the re-scan, possibly one of the above moved or reworded (${q.fresh.length}):`,
289
- ...q.fresh.map(row),
290
- `Answer with ${verbs.match(q.key)} that are the SAME finding. Every unpaired old one becomes fixed (a dismissed one stays dismissed); every unpaired fresh one stays new.`,
291
- ].join("\n");
292
- }
293
- export function renderSuggestions(sugs, settings) {
294
- const out = [];
295
- if (sugs.length === 0)
296
- out.push("No deep-lens suggestions stored — stage 1 has not suggested any yet.");
297
- else {
298
- const byLens = new Map();
299
- for (const s of sugs)
300
- byLens.set(s.lens, [...(byLens.get(s.lens) ?? []), s]);
301
- for (const [lens, ss] of [...byLens.entries()].sort((a, b) => b[1].length - a[1].length || (a[0] < b[0] ? -1 : 1))) {
302
- out.push(`${lens} (${ss.length}):`);
303
- for (const s of ss.slice(0, 30))
304
- out.push(` - [${s.unit}] ${s.files.join(", ")} — ${s.reason} (at ${s.sha.slice(0, 12)})`);
305
- if (ss.length > 30)
306
- out.push(` … and ${ss.length - 30} more`);
307
- }
308
- }
309
- if (settings) {
310
- const lenses = stage2Lenses(settings.lenses);
311
- out.push("", `Stage 2 may run: ${lenses.map((l) => `${l} (${lensTiers(settings.lenses, l, settings.riskPasses ?? []).join("|")})`).join(", ")}.`, `One row reads at most ${settings.scan.case_max_files} files / ${settings.scan.case_max_lines} lines, paired tests included; at most ${MAX_STAGE2_ROWS} rows.`, settings.scan.depth === "deep"
312
- ? `Depth: deep — a proposal adds the floor after your rows (${settings.scan.deep_lenses.filter((l) => lenses.includes(l)).join(", ") || "no lens: every deep_lenses lens is switched off"} over every file stage 1 reads; not counted in the ${MAX_STAGE2_ROWS}), so your rows may be none. deep: false proposes a standard plan.`
313
- : "Depth: standard — deep: true on a proposal makes it deep (the floor, every lens told to dig).");
314
- }
315
- return out.join("\n");
316
- }
317
- /** The floor's rows, one line per lens: its row ids, files, cost and what
318
- * the budget left out — the floor is bounded by the tree, so its rows are
319
- * summed, never listed one by one. */
320
- function renderFloor(p) {
321
- const floor = p.rows.filter((r) => r.floor);
322
- const out = [];
323
- const lenses = [...new Set(floor.map((r) => r.lens))];
324
- for (const lens of lenses) {
325
- const rows = floor.filter((r) => r.lens === lens);
326
- const run = rows.filter((r) => r.status === "run");
327
- const over = rows.filter((r) => r.status === "over-budget");
328
- const covered = rows.filter((r) => r.status === "covered");
329
- const ids = rows.length === 1 ? rows[0].id : `${rows[0].id}…${rows[rows.length - 1].id}`;
330
- out.push(` ${lens} (floor ${ids}) at ${rows[0].tier}: ${rows.length} row(s) over ${[...new Set(rows.flatMap((r) => r.units))].length} unit(s), ` +
331
- `${rows.reduce((n, r) => n + r.files.length, 0)} file(s), ${rows.reduce((n, r) => n + r.lines, 0)} lines — ` +
332
- `${run.length} to run, ~${usd(run.reduce((n, r) => n + r.estimate_usd, 0))} (${priceLabel(rows[0].price)})` +
333
- (covered.length ? `; ${covered.length} covered (free)` : "") +
334
- (over.length
335
- ? `; OVER BUDGET, left out: ${over.map((r) => r.id).join(", ")} (${usd(over.reduce((n, r) => n + r.estimate_usd, 0))})`
336
- : ""));
337
- }
338
- const f = p.floor;
339
- if (f) {
340
- if (f.lenses.length === 0)
341
- out.push(" The floor has no lens: every [scan] deep_lenses lens is switched off.");
342
- if (f.skipped.length > 0)
343
- out.push(` Switched off by the profile, no floor rows: ${f.skipped.join(", ")}.`);
344
- if (f.covered > 0 || f.named > 0)
345
- out.push(` Left out of the floor: ${f.covered} file × lens already read deep at this content` +
346
- (f.named > 0 ? `, ${f.named} named by your rows for the same lens` : "") +
347
- ".");
348
- }
349
- return out;
350
- }
351
- export function renderProposal(p, verbs = TOOL_VERBS) {
352
- const out = [];
353
- const run = p.rows.filter((r) => r.status === "run");
354
- const deep = p.depth === "deep";
355
- out.push(`STAGE 2 proposal ${deep ? "(deep) " : ""}at ${p.sha.slice(0, 12)} — ${run.length} row(s) to run, estimate ${usd(p.cap_usd)} (the run stops hard there)` +
356
- (p.budget_usd !== null ? `, fitted to a ${usd(p.budget_usd)} budget` : "") +
357
- `; validator ${p.validator ? "on" : "off"}.`);
358
- if (deep) {
359
- const own = run.filter((r) => !r.floor);
360
- const floor = run.filter((r) => r.floor);
361
- const sum = (rs) => rs.reduce((n, r) => n + r.estimate_usd, 0);
362
- out.push(`DEEP: your rows ${own.length} to run, ~${usd(sum(own))}; the floor ${floor.length} to run, ~${usd(sum(floor))}. ` +
363
- "Every lens is told to dig past its row's files; a standard reading covers nothing here, and the floor runs after your rows (the budget fits them first).");
364
- }
365
- const rows = p.rows
366
- .filter((r) => !r.floor)
367
- .map((r) => {
368
- const mark = r.status === "run"
369
- ? ""
370
- : r.status === "covered"
371
- ? " — COVERED (already read at this content, free)"
372
- : ` — OVER BUDGET (${usd(r.estimate_usd)}, left out)`;
373
- const price = priceLabel(r.price);
374
- return [
375
- ` ${r.id}: ${r.lens} at ${r.tier} over ${r.units.join(" + ")}${r.units.length > 1 ? " (cross-module)" : ""} — ${r.files.length} file(s)` +
376
- (r.tests.length ? ` + ${r.tests.length} paired test(s)` : "") +
377
- `, ${r.lines} lines, ~${usd(r.estimate_usd)} (${price})${mark}`,
378
- ` ${r.reason}`,
379
- ` files: ${r.files.map((f) => f.path).join(", ")}`,
380
- ...(r.tests.length ? [` tests: ${r.tests.map((f) => f.path).join(", ")}`] : []),
381
- ...(r.unpaired.length ? [` not paired (row full): ${r.unpaired.join(", ")}`] : []),
382
- ].join("\n");
383
- });
384
- if (rows.length > 0)
385
- out.push(rows.join("\n"));
386
- if (deep)
387
- out.push(["The deep floor:", ...renderFloor(p)].join("\n"));
388
- const priced = p.rows.filter((r) => r.status !== "covered");
389
- const measured = priced.filter((r) => priceIsMeasured(r.price)).length;
390
- if (priced.length > 0) {
391
- out.push(`Priced by measurement: ${measured} row(s); by estimate: ${priced.length - measured} row(s).` +
392
- (measured < priced.length
393
- ? " An estimate stands for this run once approved; the next proposal is priced from what the run measures."
394
- : ""));
395
- }
396
- if (p.over_budget_usd > 0)
397
- out.push(`Left out by the budget: ${usd(p.over_budget_usd)} of rows.`);
398
- if (p.runnable > 0) {
399
- out.push(`To run it: ${verbs.stage2Start(p.key)}`);
400
- }
401
- else
402
- out.push("✗ nothing to run: every row is covered or over the budget.");
403
- return out.join("\n\n");
404
- }
405
- // ---- shared verbs ------------------------------------------------------------
406
- //
407
- // What a tool and its `qcr scan` subcommand (src/scancli.ts) both answer:
408
- // the checks, the orders and the structured results. Each surface renders
409
- // them its own way; neither carries a rule of its own.
410
- /** A preview's structured result: its cases summarised (counts, not files). */
411
- export function previewDetails(p) {
412
- return {
413
- ...p,
414
- cases: p.cases.map((c) => ({
415
- id: c.id,
416
- files: c.files.length,
417
- lines: c.lines,
418
- estimate_usd: c.estimate_usd,
419
- })),
420
- };
421
- }
422
- /** A started run, as a start answers it. */
423
- export function startedDetails(run) {
424
- return {
425
- id: run.id,
426
- mode: run.mode,
427
- sha: run.sha,
428
- cases: run.cases.length,
429
- cap_usd: run.cap_usd,
430
- };
431
- }
432
- export { FINDING_STATES } from "../src/setup/findings.js";
433
- /** How many findings a listing shows unless told otherwise, and at most. */
434
- export const FINDINGS_LIMIT = 50;
435
- export const FINDINGS_LIMIT_MAX = 200;
436
- /** A findings filter from loose parameters (a tool call's, the CLI's flags),
437
- * or what is wrong with one. */
438
- export function findingsFilter(p) {
439
- const filter = {};
440
- if (typeof p.state === "string") {
441
- if (!FINDING_STATES.includes(p.state))
442
- return { ok: false, error: `Unknown state ${JSON.stringify(p.state)}.` };
443
- filter.state = p.state;
444
- }
445
- if (typeof p.severity === "string") {
446
- if (!SEVERITIES.includes(p.severity))
447
- return { ok: false, error: `Unknown severity ${JSON.stringify(p.severity)}.` };
448
- filter.severity = p.severity;
449
- }
450
- if (typeof p.lens === "string")
451
- filter.lens = p.lens;
452
- if (typeof p.unit === "string")
453
- filter.unit = p.unit;
454
- if (typeof p.doubted === "boolean")
455
- filter.doubted = p.doubted;
456
- if (typeof p.validity === "string") {
457
- if (!VALIDITY_ORDER.includes(p.validity))
458
- return {
459
- ok: false,
460
- error: `Unknown validity ${JSON.stringify(p.validity)} — confirmed, likely, doubtful or unknown.`,
461
- };
462
- filter.validity = p.validity;
463
- }
464
- return { ok: true, filter };
465
- }
466
- /** The stored findings a filter keeps, in validity order (`byValidity`),
467
- * with the count in each tier — or why the store cannot be read. */
468
- export function listFindings(findings, filter) {
469
- const { findings: listed, error } = findings.list(filter);
470
- if (error !== null)
471
- return { ok: false, error };
472
- const fs = byValidity(listed);
473
- const validity = Object.fromEntries(VALIDITY_ORDER.map((v) => [v, fs.filter((f) => validityOf(f) === v).length]));
474
- return { ok: true, findings: fs, validity };
475
- }
476
- /** Answer the match question whole, then close the tickets of the promoted
477
- * findings it fixed — and the lines that say what was settled. */
478
- export async function answerMatch(findings, args, afterMatch) {
479
- const r = findings.match({ key: args.key, pairs: args.pairs ?? [] });
480
- if (!r.ok)
481
- return r;
482
- const lines = [
483
- `Settled: ${r.matched} matched (state kept), ${r.fixed} fixed, ${r.new} new` +
484
- (r.dismissed_unreported > 0
485
- ? `, ${r.dismissed_unreported} dismissed kept dismissed (stamped no longer reported)`
486
- : "") +
487
- ".",
488
- ];
489
- let tickets = null;
490
- if (afterMatch && r.fixed > 0) {
491
- tickets = await afterMatch();
492
- if (tickets.closed.length > 0)
493
- lines.push(`Closed as fixed: ${tickets.closed.map((c) => c.ref).join(", ")}.`);
494
- for (const p of tickets.problems)
495
- lines.push(`! ticket not closed: ${p}`);
496
- }
497
- return { ...r, tickets, lines };
498
- }
499
- /** A stored proposal's summary, as a propose answers it. */
500
- export function proposalDetails(p) {
501
- const floor = p.rows.filter((x) => x.floor);
502
- return {
503
- key: p.key,
504
- sha: p.sha,
505
- cap_usd: p.cap_usd,
506
- runnable: p.runnable,
507
- rows: p.rows.length,
508
- depth: p.depth ?? "standard",
509
- floor_rows: floor.length,
510
- floor_usd: floor.filter((x) => x.status === "run").reduce((n, x) => n + x.estimate_usd, 0),
511
- };
512
- }
513
- /** The line a stage-2 start answers: how many rows run (of how many, when
514
- * narrowed), and the hard stop. */
515
- export function stage2StartedLine(run, runnable, narrowed, verbs = TOOL_VERBS) {
516
- const count = narrowed && runnable !== null
517
- ? `${run.cases.length} row(s) of ${runnable} (${run.cases.map((c) => c.id).join(", ")})`
518
- : `${run.cases.length} row(s)`;
519
- return (`Stage 2 ${run.id} started at ${run.sha.slice(0, 12)}: ${count}, hard stop ${usd(run.cap_usd)}. ` +
520
- `It runs in the background — ${verbs.status} shows progress, ${verbs.stop} stops it.`);
521
- }
522
- /** The line a stage-1 start answers. */
523
- export function startedLine(run, verbs = TOOL_VERBS) {
524
- return (`Scan ${run.id} (${run.mode}) started at ${run.sha.slice(0, 12)}: ${run.cases.length} case(s), hard stop ${usd(run.cap_usd)}. ` +
525
- `It runs in the background — ${verbs.status} shows progress, ${verbs.stop} stops it.`);
526
- }
527
- /** How often a stop through the channel looks for its answer, and how long
528
- * it waits for one. */
529
- export const STOP_POLL_MS = 2000;
530
- export const STOP_WAIT_MS = 15_000;
531
- /** Stop the recorded run: here when this runner drives it, else through the
532
- * stop channel the driving process answers — the companion, or a
533
- * `qcr scan` driver. No answer within STOP_WAIT_MS: nothing drives it. */
534
- export async function stopScan(scan, ch) {
535
- const sleep = ch.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
536
- const now = ch.now ?? (() => Date.now());
537
- const s = scan.status();
538
- const run = s.run;
539
- if (run === null || run.state !== "running")
540
- return { ok: false, errors: ["no scan is recorded as running"] };
541
- if (s.active) {
542
- const r = scan.stop();
543
- if (!r.ok)
544
- return r;
545
- return { ok: true, run: r.id, answer: `scan ${r.id} stopped — what finished is in the ledger` };
546
- }
547
- const asked = clickStopScan(ch.home, run.id, new Date(now()), ch.by ?? "companion");
548
- if (!asked.ok)
549
- return { ok: false, errors: [asked.line] };
550
- const t0 = now();
551
- for (;;) {
552
- const answer = scanStopOutcome(ch.home, asked.request);
553
- if (answer !== null) {
554
- const read = readStopResult(ch.home);
555
- const ok = read.kind === "read" && read.result.request === asked.request && read.result.ok === true;
556
- return ok ? { ok: true, run: run.id, answer } : { ok: false, errors: [answer] };
557
- }
558
- if (now() - t0 >= STOP_WAIT_MS)
559
- return {
560
- ok: false,
561
- errors: [
562
- `no process answered the stop of ${run.id} within ${STOP_WAIT_MS / 1000}s — nothing is driving it ` +
563
- "(it reads as interrupted); preview and start again to resume",
564
- ],
565
- };
566
- await sleep(STOP_POLL_MS);
567
- }
568
- }
47
+ // The rendering and the shared verbs are
48
+ // packages/pi-plugin/src/scan/scanrender.ts's, shared with `qcr scan` and the
49
+ // pi plugin's /qcr-scan.
50
+ export { CLI_VERBS, FINDINGS_LIMIT, FINDINGS_LIMIT_MAX, FINDING_STATES, STOP_POLL_MS, STOP_WAIT_MS, TOOL_VERBS, answerMatch, byValidity, findingsFilter, listFindings, previewDetails, proposalDetails, renderFindings, renderPreview, renderProposal, renderQuestion, renderStatus, renderSuggestions, stage2StartedLine, startedDetails, startedLine, stopScan, } from "@quickchatai/pi-quickreview/src/scan/scanrender.js";
569
51
  // ---- tool belt ---------------------------------------------------------------
570
52
  const DELTA_PARAM = {
571
53
  type: "boolean",
@@ -896,11 +378,12 @@ export function scanLeafRunner(proc, root, env, sdkLeaves) {
896
378
  },
897
379
  };
898
380
  }
899
- /** The scan runner the companion's belt drives: the reviewed checkout as a
381
+ /** The scan runner the companion's belt drives
382
+ * (packages/pi-plugin/src/scan/scanassemble.ts): the reviewed checkout as a
900
383
  * source of trees, the state home for every record, the saved unit cut from
901
384
  * the setup engine, the live config under the profile, and a case runner
902
- * over the scan agent build — and the triage over its findings:
903
- * reviewer memory and the tracker sink are the engine's own, handed in. */
385
+ * over the scan agent build — and the triage over its findings: reviewer
386
+ * memory and the tracker sink are the engine's own, handed in. */
904
387
  export function buildScan(engine, reviewedRepo, env, shared = null,
905
388
  /** The driver lease; default the state home's file lease, shared with every
906
389
  * other runner over it. */
@@ -911,69 +394,28 @@ lease = null,
911
394
  sdkLeaves) {
912
395
  const root = env.QCR_ROOT?.trim() || sandboxRoot();
913
396
  const home = stateHome(env);
914
- const state = stateHomeFs(home);
915
- const agentsDir = join(home, SCAN_AGENTS_DIR);
916
- const settingsOf = () => {
917
- const loaded = loadScanConfig(root, env);
918
- return loaded.ok ? scanSettings(loaded.cfg) : null;
919
- };
920
- const findings = new FindingsStore(state);
921
- const transcriptDir = join(home, SCAN_TRANSCRIPTS_DIR);
922
- // A token a case would not outlive stops the run, on either host.
923
- const stopOnExpired = (reason) => {
924
- console.error(`scan: stopping — ${reason}`);
925
- scan.stop();
926
- };
927
397
  // Assigned below: the triage reads the runner's runs, the runner closes
928
398
  // the triage's tickets at every run's end.
929
399
  let triage = null;
930
- const scan = new ScanRunner({
931
- state,
932
- clock: () => new Date(),
400
+ const { scan, findings, settingsOf } = assembleScan({
401
+ home,
402
+ reviewedRepo,
403
+ root,
404
+ config: () => loadScanConfig(root, env),
405
+ profiles: homeProfileLookup(env),
406
+ unitCut: engine,
933
407
  // In the scan container: each case on a fresh refresh-disabled copy of
934
408
  // the companion's login; a token a case would not outlive stops the run.
935
- cases: scanCasesForEnv(scanLeafRunner(piCaseRunner({ agentsDir, cwd: root, transcriptDir }), root, env, sdkLeaves &&
936
- (() => sdkLeaves({ agentsDir, cwd: root, transcriptDir, env, onExpired: stopOnExpired }))), env, stopOnExpired),
937
- source: gitScanSource(reviewedRepo, home),
938
- savedCut: () => {
939
- const cut = engine.savedCut();
940
- if (cut)
941
- return { ok: true, cut };
942
- const status = engine.status();
943
- return {
944
- ok: false,
945
- errors: status.cut_file_unreadable
946
- ? status.problems
947
- : ["no saved unit cut — a scan reads by unit; propose, adjust and save one first"],
948
- };
949
- },
950
- settings: () => {
951
- const loaded = loadScanConfig(root, env);
952
- return loaded.ok ? { ok: true, settings: scanSettings(loaded.cfg) } : loaded;
953
- },
954
- // At the depth the runner planned at — a stage-2 proposal's own, which
955
- // its `deep` may have set apart from `[scan] depth`.
956
- buildAgents: (settings) => {
957
- const loaded = loadScanConfig(root, env);
958
- if (!loaded.ok)
959
- return loaded;
960
- const cfg = { ...loaded.cfg, scan: { ...loaded.cfg.scan, depth: settings.scan.depth } };
961
- try {
962
- buildScanAgents(root, agentsDir, cfg, homeProfileLookup(env));
963
- return { ok: true };
964
- }
965
- catch (err) {
966
- return { ok: false, errors: [err instanceof Error ? err.message : String(err)] };
967
- }
968
- },
409
+ cases: ({ agentsDir, transcriptDir, stop }) => scanCasesForEnv(scanLeafRunner(piCaseRunner({ agentsDir, cwd: root, transcriptDir }), root, env, sdkLeaves &&
410
+ (() => sdkLeaves({ agentsDir, cwd: root, transcriptDir, env, onExpired: stop }))), env, stop),
969
411
  closeFixedTickets: async () => (triage ? triage.closeFixed() : { closed: [], problems: [] }),
970
- lease: lease ?? fileLease(home),
412
+ lease,
971
413
  log: (line) => console.error(line),
972
414
  });
973
415
  if (shared) {
974
416
  triage = new ScanTriage({
975
417
  findings,
976
- state,
418
+ state: stateHomeFs(home),
977
419
  memory: shared.memory,
978
420
  tracker: shared.tracker,
979
421
  clock: () => new Date(),
@@ -983,16 +425,5 @@ sdkLeaves) {
983
425
  }
984
426
  return { scan, findings, settingsOf, triage, home };
985
427
  }
986
- /** The runner's runs, newest first, as the triage sees them: a run left
987
- * `running` by a companion that is gone reads as interrupted. */
988
- export function scanRuns(scan) {
989
- const s = scan.status();
990
- const out = [];
991
- if (s.run)
992
- out.push({ id: s.run.id, sha: s.run.sha, state: s.run.display });
993
- for (const h of s.history)
994
- if (!out.some((r) => r.id === h.id))
995
- out.push({ id: h.id, sha: h.sha, state: h.state });
996
- return out;
997
- }
428
+ export { scanRuns };
998
429
  //# sourceMappingURL=setup-scan.js.map