@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,523 +1,3 @@
1
- // The unit cut (setup & scan spec "Unit"): the
2
- // partition of a repo into stack-aware units — a Django app, a package, a
3
- // workspace — that the survey reads, the scan reviews and every report groups
4
- // by. The COMPANION proposes it, the OPERATOR adjusts it, and once saved it is
5
- // reused by every later survey and scan; the companion re-cuts only on request.
6
- //
7
- // A unit OWNS path prefixes (`roots`). A file belongs to the unit whose root is
8
- // the LONGEST prefix of it, so units nest naturally (`backend` and
9
- // `backend/payments` are two units, and `backend/payments/x.py` is the
10
- // second's); an `ignored` prefix is the same kind of claim held by nobody, so
11
- // `backend/vendor` can be carved out of `backend` and a unit inside an ignored
12
- // tree takes its files back. The root `""` claims everything, which is how a
13
- // catch-all "rest" unit is spelt. A SAVED cut is a partition: every file is in
14
- // exactly one unit or deliberately ignored — a file nobody claims refuses the
15
- // save, because a scan that silently skips it would read as "covered".
16
- //
17
- // Pure: the cut is a value, validation and the adjust ops are functions over
18
- // it and a file listing. The engine persists it (src/setup/engine.ts).
19
- import { SCAN_DEFAULTS } from "../config.js";
20
- import { displayRoot, UNIT_KINDS, UNIT_NAME_MAX, UNIT_NAME_RE, type UnitKind } from "./facts.js";
21
-
22
- export interface Unit {
23
- name: string;
24
- roots: string[];
25
- kind: UnitKind;
26
- /** Why this unit exists — the marker that proposed it, or the operator's
27
- * reason. Shown beside the unit; never parsed. */
28
- note: string;
29
- }
30
-
31
- export interface UnitCut {
32
- units: Unit[];
33
- /** Prefixes nobody reviews (vendored, generated, fixtures the operator
34
- * chose to leave out). */
35
- ignored: string[];
36
- }
37
-
38
- // ---- normalisation -----------------------------------------------------------
39
-
40
- /** A root as stored: repo-relative, no leading `./` or `/`, no trailing `/`.
41
- * Null for anything that could leave the repo (`..`, an absolute path after
42
- * stripping is fine — it is relative by construction — but `..` is not). */
43
- export function normalizeRoot(raw: string): string | null {
44
- let r = raw.trim().replace(/\\/g, "/");
45
- while (r.startsWith("./")) r = r.slice(2);
46
- r = r
47
- .replace(/^\/+/, "")
48
- .replace(/\/+$/, "")
49
- .replace(/\/{2,}/g, "/");
50
- if (r === ".") r = "";
51
- if (r.split("/").some((s) => s === ".." || s === ".")) return null;
52
- return r;
53
- }
54
-
55
- function covers(root: string, path: string): boolean {
56
- return root === "" || path === root || path.startsWith(`${root}/`);
57
- }
58
-
59
- // ---- assignment --------------------------------------------------------------
60
-
61
- export interface Assignment {
62
- /** unit name → its files, in listing order. Every unit has an entry. */
63
- byUnit: Map<string, string[]>;
64
- ignored: string[];
65
- unassigned: string[];
66
- }
67
-
68
- /** Longest-prefix assignment of every listed file. */
69
- export function assign(cut: UnitCut, files: readonly string[]): Assignment {
70
- const claims: Array<{ root: string; unit: string | null }> = [
71
- ...cut.units.flatMap((u) => u.roots.map((root) => ({ root, unit: u.name }))),
72
- ...cut.ignored.map((root) => ({ root, unit: null })),
73
- ].sort((a, b) => b.root.length - a.root.length);
74
- const byUnit = new Map<string, string[]>(cut.units.map((u) => [u.name, []]));
75
- const ignored: string[] = [];
76
- const unassigned: string[] = [];
77
- for (const f of files) {
78
- const hit = claims.find((c) => covers(c.root, f));
79
- if (!hit) unassigned.push(f);
80
- else if (hit.unit === null) ignored.push(f);
81
- else byUnit.get(hit.unit)!.push(f);
82
- }
83
- return { byUnit, ignored, unassigned };
84
- }
85
-
86
- // ---- validation --------------------------------------------------------------
87
-
88
- export interface CutProblems {
89
- /** Refusals: the cut cannot be stored at all. */
90
- errors: string[];
91
- /** Fine for a draft, refused by save (an empty unit, unclaimed files). */
92
- blocking_save: string[];
93
- }
94
-
95
- const SAMPLE = 8;
96
-
97
- /** Refused both when a proposal is read and when a cut is checked. */
98
- const ROOT_IGNORED = "the repo root cannot be ignored — that would leave nothing to review";
99
-
100
- function sample(paths: readonly string[]): string {
101
- const head = paths.slice(0, SAMPLE).join(", ");
102
- return paths.length > SAMPLE ? `${head}, … (+${paths.length - SAMPLE} more)` : head;
103
- }
104
-
105
- /**
106
- * Structural refusals (bad names, duplicate names or roots, a root that could
107
- * leave the repo or matches no file, an ignored repo root) and the two
108
- * save-only conditions (a unit that ends up with no files, files no unit
109
- * claims). The cut is expected normalised (see `normalizeCut`).
110
- */
111
- export function checkCut(cut: UnitCut, files: readonly string[]): CutProblems {
112
- const errors: string[] = [];
113
- const blocking: string[] = [];
114
- if (cut.units.length === 0) errors.push("a unit cut needs at least one unit");
115
- const names = new Set<string>();
116
- const roots = new Map<string, string>();
117
- for (const u of cut.units) {
118
- if (!UNIT_NAME_RE.test(u.name) || u.name.includes("..")) {
119
- errors.push(
120
- `unit name ${JSON.stringify(u.name)} is not a plain name — letters, digits, '.', '_' and '-', at most ${UNIT_NAME_MAX}`,
121
- );
122
- }
123
- if (names.has(u.name)) errors.push(`unit name "${u.name}" is used twice`);
124
- names.add(u.name);
125
- if (u.roots.length === 0) errors.push(`unit "${u.name}" owns no root`);
126
- for (const r of u.roots) {
127
- const owner = roots.get(r);
128
- if (owner !== undefined)
129
- errors.push(`root "${displayRoot(r)}" is claimed by both "${owner}" and "${u.name}"`);
130
- roots.set(r, u.name);
131
- }
132
- }
133
- for (const r of cut.ignored) {
134
- if (r === "") errors.push(ROOT_IGNORED);
135
- const owner = roots.get(r);
136
- if (owner !== undefined) errors.push(`root "${r}" is both ignored and owned by "${owner}"`);
137
- roots.set(r, "(ignored)");
138
- }
139
- for (const [r, owner] of roots) {
140
- if (r !== "" && !files.some((f) => covers(r, f))) {
141
- errors.push(`root "${r}" (${owner}) matches no file in the repo`);
142
- }
143
- }
144
- if (errors.length === 0) {
145
- const a = assign(cut, files);
146
- for (const [name, owned] of a.byUnit) {
147
- if (owned.length === 0) {
148
- blocking.push(
149
- `unit "${name}" ends up with no files — every file under its roots belongs to a deeper unit or is ignored`,
150
- );
151
- }
152
- }
153
- if (a.unassigned.length > 0) {
154
- blocking.push(
155
- `${a.unassigned.length} file(s) belong to no unit: ${sample(a.unassigned)} — add them to a unit, add a catch-all unit with root "", or ignore them`,
156
- );
157
- }
158
- }
159
- return { errors, blocking_save: blocking };
160
- }
161
-
162
- /** Raw input (a tool call's JSON) → a normalised cut, or the reasons it is not
163
- * one. Normalisation never guesses: an unspellable root is an error, not a
164
- * silently-dropped entry. */
165
- export function normalizeCut(
166
- raw: unknown,
167
- ): { ok: true; cut: UnitCut } | { ok: false; errors: string[] } {
168
- const errors: string[] = [];
169
- const obj = (raw ?? {}) as { units?: unknown; ignored?: unknown };
170
- if (!Array.isArray(obj.units))
171
- return { ok: false, errors: ["`units` must be an array of {name, roots, kind?, note?}"] };
172
- const units: Unit[] = [];
173
- for (const [i, u] of obj.units.entries()) {
174
- const unit = (u ?? {}) as { name?: unknown; roots?: unknown; kind?: unknown; note?: unknown };
175
- if (typeof unit.name !== "string") {
176
- errors.push(`units[${i}]: name must be a string`);
177
- continue;
178
- }
179
- const rawRoots = typeof unit.roots === "string" ? [unit.roots] : unit.roots;
180
- if (!Array.isArray(rawRoots) || rawRoots.some((r) => typeof r !== "string")) {
181
- errors.push(`unit "${unit.name}": roots must be an array of repo-relative paths`);
182
- continue;
183
- }
184
- const roots = normalizeRoots(rawRoots as string[], `unit "${unit.name}"`, errors);
185
- units.push({
186
- name: unit.name.trim(),
187
- roots,
188
- kind: unitKind(unit.kind),
189
- note: typeof unit.note === "string" ? unit.note.trim() : "",
190
- });
191
- }
192
- let ignored: string[] = [];
193
- if (obj.ignored !== undefined) {
194
- if (!Array.isArray(obj.ignored) || obj.ignored.some((r) => typeof r !== "string")) {
195
- errors.push("`ignored` must be an array of repo-relative paths");
196
- } else {
197
- ignored = normalizeRoots(obj.ignored as string[], "ignored", errors);
198
- if (ignored.includes("")) errors.push(ROOT_IGNORED);
199
- }
200
- }
201
- return errors.length > 0 ? { ok: false, errors } : { ok: true, cut: { units, ignored } };
202
- }
203
-
204
- /** A supplied kind, or `custom` for anything that is not one of `UNIT_KINDS`. */
205
- function unitKind(raw: unknown): UnitKind {
206
- return typeof raw === "string" && new Set<string>(UNIT_KINDS).has(raw)
207
- ? (raw as UnitKind)
208
- : "custom";
209
- }
210
-
211
- function normalizeRoots(raw: readonly string[], owner: string, errors: string[]): string[] {
212
- const out: string[] = [];
213
- for (const r of raw) {
214
- const n = normalizeRoot(r);
215
- if (n === null) errors.push(`${owner}: root ${JSON.stringify(r)} leaves the repo`);
216
- else if (!out.includes(n)) out.push(n);
217
- }
218
- return out;
219
- }
220
-
221
- // ---- adjust ops --------------------------------------------------------------
222
-
223
- /** The operator's corrections, as the companion relays them. Applied to a
224
- * draft all-or-nothing: one refused op leaves the draft untouched. */
225
- export type AdjustOp =
226
- | { op: "rename"; unit: string; to: string }
227
- | { op: "add"; name: string; roots: string[]; kind?: string; note?: string }
228
- | { op: "remove"; unit: string }
229
- | { op: "merge"; units: string[]; into: string }
230
- | { op: "move"; root: string; to: string }
231
- | { op: "set_roots"; unit: string; roots: string[] }
232
- | { op: "ignore"; roots: string[] }
233
- | { op: "unignore"; roots: string[] }
234
- | { op: "note"; unit: string; note: string };
235
-
236
- export const ADJUST_OPS = [
237
- "rename",
238
- "add",
239
- "remove",
240
- "merge",
241
- "move",
242
- "set_roots",
243
- "ignore",
244
- "unignore",
245
- "note",
246
- ] as const;
247
-
248
- function clone(cut: UnitCut): UnitCut {
249
- return {
250
- units: cut.units.map((u) => ({ ...u, roots: [...u.roots] })),
251
- ignored: [...cut.ignored],
252
- };
253
- }
254
-
255
- function strings(v: unknown): string[] | null {
256
- if (typeof v === "string") return [v];
257
- return Array.isArray(v) && v.every((x) => typeof x === "string") ? v : null;
258
- }
259
-
260
- /**
261
- * Apply `ops` in order to a copy of `cut`. Returns the new cut, or every
262
- * reason it could not (unknown unit, unknown op, a root that leaves the repo).
263
- * The structural check of the RESULT is the caller's (`checkCut`) — an op
264
- * sequence may pass through an invalid intermediate state (swap two names).
265
- */
266
- export function applyOps(
267
- cut: UnitCut,
268
- ops: readonly unknown[],
269
- ): { ok: true; cut: UnitCut } | { ok: false; errors: string[] } {
270
- const next = clone(cut);
271
- const errors: string[] = [];
272
- const unit = (name: unknown, i: number): Unit | null => {
273
- const u = typeof name === "string" ? next.units.find((x) => x.name === name) : undefined;
274
- if (!u)
275
- errors.push(
276
- `ops[${i}]: no unit named ${JSON.stringify(name)} — units: ${next.units.map((x) => x.name).join(", ")}`,
277
- );
278
- return u ?? null;
279
- };
280
- const rootsOf = (v: unknown, i: number): string[] | null => {
281
- const raw = strings(v);
282
- if (raw === null) {
283
- errors.push(`ops[${i}]: roots must be a path or an array of paths`);
284
- return null;
285
- }
286
- return normalizeRoots(raw, `ops[${i}]`, errors);
287
- };
288
- const release = (roots: readonly string[]): void => {
289
- for (const u of next.units) u.roots = u.roots.filter((r) => !roots.includes(r));
290
- next.ignored = next.ignored.filter((r) => !roots.includes(r));
291
- };
292
- for (const [i, raw] of ops.entries()) {
293
- const o = (raw ?? {}) as Record<string, unknown>;
294
- switch (o.op) {
295
- case "rename": {
296
- const u = unit(o.unit, i);
297
- if (u && typeof o.to === "string") u.name = o.to.trim();
298
- else if (u) errors.push(`ops[${i}]: rename needs \`to\``);
299
- break;
300
- }
301
- case "add": {
302
- const roots = rootsOf(o.roots, i);
303
- if (typeof o.name !== "string") errors.push(`ops[${i}]: add needs \`name\``);
304
- else if (roots) {
305
- release(roots);
306
- next.units.push({
307
- name: o.name.trim(),
308
- roots,
309
- kind: unitKind(o.kind),
310
- note: typeof o.note === "string" ? o.note.trim() : "added by the operator",
311
- });
312
- }
313
- break;
314
- }
315
- case "remove": {
316
- const u = unit(o.unit, i);
317
- if (u) next.units = next.units.filter((x) => x !== u);
318
- break;
319
- }
320
- case "merge": {
321
- const names = strings(o.units);
322
- if (!names || names.length < 2 || typeof o.into !== "string") {
323
- errors.push(`ops[${i}]: merge needs \`units\` (two or more) and \`into\``);
324
- break;
325
- }
326
- const members = names.map((n) => unit(n, i));
327
- if (members.some((m) => m === null)) break;
328
- const into = o.into.trim();
329
- const keep = members.find((m) => m!.name === into) ?? members[0]!;
330
- keep.roots = [...new Set(members.flatMap((m) => m!.roots))];
331
- keep.name = into;
332
- keep.note = keep.note
333
- ? `${keep.note}; merged from ${names.join(", ")}`
334
- : `merged from ${names.join(", ")}`;
335
- next.units = next.units.filter((x) => x === keep || !members.includes(x));
336
- break;
337
- }
338
- case "move": {
339
- const roots = rootsOf(o.root, i);
340
- const u = unit(o.to, i);
341
- if (roots && u) {
342
- release(roots);
343
- u.roots.push(...roots);
344
- }
345
- break;
346
- }
347
- case "set_roots": {
348
- const u = unit(o.unit, i);
349
- const roots = rootsOf(o.roots, i);
350
- if (u && roots) {
351
- release(roots);
352
- u.roots = roots;
353
- }
354
- break;
355
- }
356
- case "ignore": {
357
- const roots = rootsOf(o.roots, i);
358
- if (roots) {
359
- release(roots);
360
- next.ignored.push(...roots);
361
- }
362
- break;
363
- }
364
- case "unignore": {
365
- const roots = rootsOf(o.roots, i);
366
- if (roots) {
367
- const missing = roots.filter((r) => !next.ignored.includes(r));
368
- if (missing.length > 0) errors.push(`ops[${i}]: not ignored: ${missing.join(", ")}`);
369
- next.ignored = next.ignored.filter((r) => !roots.includes(r));
370
- }
371
- break;
372
- }
373
- case "note": {
374
- const u = unit(o.unit, i);
375
- if (u && typeof o.note === "string") u.note = o.note.trim();
376
- else if (u) errors.push(`ops[${i}]: note needs \`note\``);
377
- break;
378
- }
379
- default:
380
- errors.push(
381
- `ops[${i}]: unknown op ${JSON.stringify(o.op)} — one of ${ADJUST_OPS.join(", ")}`,
382
- );
383
- }
384
- }
385
- return errors.length > 0 ? { ok: false, errors } : { ok: true, cut: next };
386
- }
387
-
388
- // ---- cases: an oversized unit split to fit, its name kept --------------------
389
-
390
- /** How much one case may read. The default is one PR round's sweep capacity
391
- * (`sweep_max_groups` × a sweep group, 6 × 5 files / 500 lines): a case is one
392
- * main pi fanning out leaf lenses, so it holds what a round holds. The scan's
393
- * `[scan]` config is where a repo tunes it. */
394
- export interface CaseBudget {
395
- maxFiles: number;
396
- maxLines: number;
397
- }
398
-
399
- export const DEFAULT_CASE_BUDGET: CaseBudget = {
400
- maxFiles: SCAN_DEFAULTS.case_max_files,
401
- maxLines: SCAN_DEFAULTS.case_max_lines,
402
- };
403
-
404
- export interface CasePlan {
405
- /** The UNIT's name — never the part's. Reports group by this. */
406
- unit: string;
407
- /** `<unit>` when the unit fits, `<unit>#<i>` (1-based) when it was split. */
408
- id: string;
409
- part: number;
410
- parts: number;
411
- files: string[];
412
- lines: number;
413
- /** One file alone over the budget: it is its own case here — this split
414
- * never cuts a file. The SCAN then reads such a file in line ranges, one
415
- * case per range under the unit's name (scan.ts cutRanges). */
416
- over_budget: boolean;
417
- }
418
-
419
- interface Piece {
420
- files: string[];
421
- lines: number;
422
- }
423
-
424
- /**
425
- * Split one unit's files into cases that fit `budget`: subdirectory first
426
- * (a directory that fits stays whole), then file chunks in path order; small
427
- * neighbouring pieces are packed back together so a split never produces a
428
- * case per tiny directory. Every case keeps the unit's name. Deterministic:
429
- * the same files, sizes and budget always give the same cases. No files, no
430
- * case.
431
- */
432
- export function splitUnit(
433
- unit: string,
434
- files: readonly string[],
435
- linesOf: (path: string) => number,
436
- budget: CaseBudget = DEFAULT_CASE_BUDGET,
437
- ): CasePlan[] {
438
- if (files.length === 0) return [];
439
- const sorted = [...files].sort();
440
- const size = new Map(sorted.map((f) => [f, Math.max(0, linesOf(f))]));
441
- const fits = (n: number, lines: number): boolean =>
442
- n <= budget.maxFiles && lines <= budget.maxLines;
443
- const total = (fs: readonly string[]): number => fs.reduce((n, f) => n + size.get(f)!, 0);
444
-
445
- const pieces = (fs: string[], depth: number): Piece[] => {
446
- const lines = total(fs);
447
- if (fits(fs.length, lines)) return [{ files: fs, lines }];
448
- // By the next path segment below the common prefix of this depth.
449
- const groups = new Map<string, string[]>();
450
- const loose: string[] = [];
451
- for (const f of fs) {
452
- const segs = f.split("/");
453
- if (segs.length - 1 > depth) {
454
- const key = segs.slice(0, depth + 1).join("/");
455
- if (!groups.has(key)) groups.set(key, []);
456
- groups.get(key)!.push(f);
457
- } else {
458
- loose.push(f);
459
- }
460
- }
461
- // One directory holding everything: descend without spending a split.
462
- if (loose.length === 0 && groups.size === 1) return pieces(fs, depth + 1);
463
- const out: Piece[] = [];
464
- for (const key of [...groups.keys()].sort()) out.push(...pieces(groups.get(key)!, depth + 1));
465
- out.push(...chunk(loose));
466
- return out;
467
- };
468
-
469
- const chunk = (fs: readonly string[]): Piece[] => {
470
- const out: Piece[] = [];
471
- let cur: Piece = { files: [], lines: 0 };
472
- for (const f of fs) {
473
- const n = size.get(f)!;
474
- if (cur.files.length > 0 && !fits(cur.files.length + 1, cur.lines + n)) {
475
- out.push(cur);
476
- cur = { files: [], lines: 0 };
477
- }
478
- cur.files.push(f);
479
- cur.lines += n;
480
- }
481
- if (cur.files.length > 0) out.push(cur);
482
- return out;
483
- };
484
-
485
- // Pack neighbours: a directory that fit on its own may share a case with
486
- // the next one when both still fit together.
487
- const packed: Piece[] = [];
488
- for (const p of pieces(sorted, commonDepth(sorted))) {
489
- const last = packed[packed.length - 1];
490
- if (last && fits(last.files.length + p.files.length, last.lines + p.lines)) {
491
- last.files.push(...p.files);
492
- last.lines += p.lines;
493
- } else {
494
- packed.push({ files: [...p.files], lines: p.lines });
495
- }
496
- }
497
- const parts = packed.length;
498
- return packed.map((p, i) => ({
499
- unit,
500
- id: parts === 1 ? unit : `${unit}#${i + 1}`,
501
- part: i + 1,
502
- parts,
503
- files: p.files,
504
- lines: p.lines,
505
- over_budget: !fits(p.files.length, p.lines),
506
- }));
507
- }
508
-
509
- /** Depth of the directory every file shares, so a unit rooted at
510
- * `backend/payments` splits by what is BELOW it rather than re-grouping on
511
- * `backend`. */
512
- function commonDepth(files: readonly string[]): number {
513
- if (files.length === 0) return 0;
514
- const first = files[0]!.split("/").slice(0, -1);
515
- let depth = first.length;
516
- for (const f of files) {
517
- const segs = f.split("/").slice(0, -1);
518
- let d = 0;
519
- while (d < depth && d < segs.length && segs[d] === first[d]) d++;
520
- depth = d;
521
- }
522
- return depth;
523
- }
1
+ // Moved to the plugin (packages/pi-plugin/src/scan/unitcut.ts), the scan engine's
2
+ // home: this name stays for the app's importers.
3
+ export * from "@quickchatai/pi-quickreview/src/scan/unitcut.js";
package/src/state.ts CHANGED
@@ -7767,12 +7767,48 @@ export interface RoundStageBounds {
7767
7767
  export const PR_RUNTIMES = ["tmux", "worker", "answer", "idle", "debug", "takeover"] as const;
7768
7768
  export type PrRuntime = (typeof PR_RUNTIMES)[number];
7769
7769
 
7770
+ /** One open pull request as the board lists it (src/reconcile.ts lists them). */
7771
+ export interface OpenPrSummary {
7772
+ number: number;
7773
+ title: string;
7774
+ author: string;
7775
+ draft: boolean;
7776
+ updated_at: string;
7777
+ }
7778
+
7779
+ export interface OpenPrsSnapshot {
7780
+ listed_at: string;
7781
+ prs: OpenPrSummary[];
7782
+ }
7783
+
7784
+ /** The authoritative reconcile poll's last pass (status.json `poll`): what
7785
+ * "auto-review is working" is judged on, beside the heartbeat (the home's
7786
+ * label, src/home/autoreview.ts). */
7787
+ export interface PollStatus {
7788
+ /** When the last pass ran, ISO; null before the boot's first pass has ended. */
7789
+ last_at: string | null;
7790
+ /** That pass ran with no step error; null before the first pass. */
7791
+ ok: boolean | null;
7792
+ /** The first error, while not ok. */
7793
+ error?: string;
7794
+ /** The daemon's `[daemon] poll_interval_s`: freshness is judged against
7795
+ * the daemon's own cadence, never a reader's guess. */
7796
+ interval_s: number;
7797
+ }
7798
+
7770
7799
  export interface StatusSnapshot {
7771
7800
  written_at: string;
7772
7801
  dry_run: boolean;
7802
+ /** The reconcile poll's last pass. Absent in snapshots from an older
7803
+ * daemon — readers say "unknown", never "working". */
7804
+ poll?: PollStatus;
7773
7805
  /** Where the daemon runs (src/runhost.ts). Absent: not reported (an older
7774
7806
  * daemon, or one assembled without its host facts). */
7775
7807
  run_host?: import("./runhost.js").RunHostStatus;
7808
+ /** Every open pull request of the reviewed repo, as the last successful
7809
+ * reconcile pass listed it (tracked or not): what the board offers to
7810
+ * review. Absent until a pass has listed them. */
7811
+ open_prs?: OpenPrsSnapshot;
7776
7812
  prs: Array<
7777
7813
  Pick<PrStateRow, "number" | "state" | "title" | "head_sha" | "stale_recheck"> &
7778
7814
  Partial<Pick<PrStateRow, "trigger_ts" | "updated_at" | "author" | "paused">> & {
@@ -7856,6 +7892,11 @@ export interface StatusSnapshot {
7856
7892
  max_concurrent_rounds?: number;
7857
7893
  total_spend_usd: number;
7858
7894
  today_spend_usd?: number;
7895
+ /** The month's spend so far: every round started in the snapshot's UTC
7896
+ * month (the same rule as `today_spend_usd`, expired rounds included).
7897
+ * Absent in snapshots from an older daemon: the home's spend line then
7898
+ * reads unknown, never a zero. */
7899
+ month_spend_usd?: number;
7859
7900
  digest_headline?: string;
7860
7901
  /** Present while pi sessions are failing API calls with token-expired
7861
7902
  * errors (packages/pi-plugin/src/authwatch.ts, fresh < 30 min). The TUI raises an alert. */
@@ -8206,6 +8247,8 @@ export function buildStatusSnapshot(
8206
8247
  ...(maxConcurrentRounds !== undefined ? { max_concurrent_rounds: maxConcurrentRounds } : {}),
8207
8248
  total_spend_usd: store.totalSpend(),
8208
8249
  today_spend_usd: roundUsage(store.roundsOn(today)).spend_usd,
8250
+ // roundsOn matches by prefix: the month's `YYYY-MM` is every day of it
8251
+ month_spend_usd: roundUsage(store.roundsOn(today.slice(0, 7))).spend_usd,
8209
8252
  ...(digestHeadline ? { digest_headline: digestHeadline } : {}),
8210
8253
  ...(piAuth
8211
8254
  ? {
@@ -205,7 +205,10 @@ function applyEventTransaction(
205
205
  const now = opts.now ?? (() => new Date());
206
206
  const existing = store.getPr(pr);
207
207
  const author = meta.author ?? existing?.author ?? "";
208
- if (opts.botLogin && author && isBotAuthor(author, opts.botLogin)) {
208
+ // `botLogin` is the bot ACCOUNT (identity.ts `botAccountLogin`): "" in
209
+ // person mode, where the person's own PRs are the target and only
210
+ // `[bot]`-suffixed accounts are skipped. Absent ⇒ nothing is skipped.
211
+ if (opts.botLogin !== undefined && author && isBotAuthor(author, opts.botLogin)) {
209
212
  return {
210
213
  pr,
211
214
  from: existing?.state ?? null,
package/src/takeover.ts CHANGED
@@ -82,6 +82,9 @@ export interface TakeoverPlan {
82
82
  cwd: string;
83
83
  /** The login's owner dir (`pihome/agent`): read-only, borrowed from. */
84
84
  agent_dir: string;
85
+ /** The login's dir when it is not `agent_dir` (the single box's shared
86
+ * login): the login is borrowed from here, the rest from `agent_dir`. */
87
+ login_dir?: string;
85
88
  /** The PR's JSONL, which the pi resumes and appends to. */
86
89
  session_file: string;
87
90
  model: string;
@@ -597,6 +600,7 @@ function rmQuiet(file: string): void {
597
600
  export function takeoverPlanFor(o: {
598
601
  root: string;
599
602
  agentDir: string;
603
+ loginDir?: string;
600
604
  sessionsRoot: string;
601
605
  model: string;
602
606
  pr: number;
@@ -612,6 +616,7 @@ export function takeoverPlanFor(o: {
612
616
  root: o.root,
613
617
  cwd: o.root,
614
618
  agent_dir: o.agentDir,
619
+ ...(o.loginDir !== undefined && o.loginDir !== o.agentDir ? { login_dir: o.loginDir } : {}),
615
620
  session_file: file,
616
621
  model: o.model,
617
622
  ...(o.systemPromptFile ? { system_prompt_file: o.systemPromptFile } : {}),
package/src/takeoverpi.ts CHANGED
@@ -28,6 +28,7 @@ import { chmodSync, copyFileSync, existsSync, readFileSync, unlinkSync } from "n
28
28
  import { dirname, join } from "node:path";
29
29
  import { fileURLToPath } from "node:url";
30
30
  import { GITHUB_TOKEN_ENVS } from "@quickchatai/pi-quickreview/src/envstrip.js";
31
+ import { routeModelSpecForLogin } from "@quickchatai/pi-quickreview/src/modelroute.js";
31
32
  import { CAP_FD_INDEX } from "./ipc/capability.js";
32
33
  import {
33
34
  PI_REFRESH_MARGIN_MS,
@@ -90,6 +91,7 @@ export function readTakeoverSpec(file: string): TakeoverSpec {
90
91
  raw.session !== `takeover-pr-${raw.pr}` ||
91
92
  typeof raw.session_file !== "string" ||
92
93
  typeof raw.agent_dir !== "string" ||
94
+ (raw.login_dir !== undefined && typeof raw.login_dir !== "string") ||
93
95
  typeof raw.borrow_dir !== "string" ||
94
96
  typeof raw.model !== "string" ||
95
97
  typeof raw.cwd !== "string" ||
@@ -102,6 +104,15 @@ export function readTakeoverSpec(file: string): TakeoverSpec {
102
104
  return raw as TakeoverSpec;
103
105
  }
104
106
 
107
+ /** The login file in an agent dir, as text; null when there is none. */
108
+ export function readLogin(agentDir: string): string | null {
109
+ try {
110
+ return readFileSync(join(agentDir, "auth.json"), "utf8");
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
105
116
  /**
106
117
  * The pi's own agent dir: the owner's login borrowed as a refresh-blanked copy
107
118
  * (0600, refused when its token would expire inside the lease's idle window plus
@@ -109,11 +120,11 @@ export function readTakeoverSpec(file: string): TakeoverSpec {
109
120
  * the owner's dir.
110
121
  */
111
122
  export function prepareBorrowedAgent(
112
- spec: Pick<TakeoverSpec, "agent_dir" | "borrow_dir" | "idle_ms">,
123
+ spec: Pick<TakeoverSpec, "agent_dir" | "login_dir" | "borrow_dir" | "idle_ms">,
113
124
  now?: () => number,
114
125
  ): { expiresAt: number | null } {
115
126
  const { expiresAt } = copyPiAuth({
116
- srcDir: spec.agent_dir,
127
+ srcDir: spec.login_dir ?? spec.agent_dir,
117
128
  dstDir: spec.borrow_dir,
118
129
  needMs: spec.idle_ms + PI_REFRESH_MARGIN_MS,
119
130
  words: ENGINE_AUTH_WORDS,
@@ -259,6 +270,9 @@ export async function runTakeover(specFile: string, deps: TakeoverRunDeps = {}):
259
270
  return 3;
260
271
  }
261
272
  const env = takeoverEnv(procEnv, spec);
273
+ // An OpenAI model runs on the OpenAI login the borrowed copy holds
274
+ // (packages/pi-plugin/src/modelroute.ts): the spec's own provider when it is there.
275
+ spec = { ...spec, model: routeModelSpecForLogin(spec.model, readLogin(spec.borrow_dir), env) };
262
276
  const pi = paneLaunch(takeoverArgv(spec), deps.piBin ?? spec.pi_bin);
263
277
  const launch = toolIsolation(pi.bin, pi.argv, procEnv);
264
278
  if ("refused" in launch) {