@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
@@ -19,23 +19,14 @@
19
19
  // trackersink.ts's. The GitHub reader port is declared and injected, but no
20
20
  // engine behaviour calls it yet (the PR replay builds its own reader).
21
21
  //
22
- // Persistence: `<state home>/setup/unit-cut.json`, tmp+rename atomic through
23
- // the state port. It holds the saved cut (the one survey and scan reuse), the
24
- // previous saved cut (so a re-cut can be compared and undone), and at most one
25
- // draft (the companion's proposal as the operator adjusts it). A saved cut is
26
- // never touched by a propose or an adjust — only `save` replaces it. An
27
- // unreadable file is reported and refuses every write: it may hold the
28
- // operator's saved cut, and silently starting fresh over it would lose that.
22
+ // Persistence: the unit cut is unitcutstore.ts's, `<state home>/setup/unit-cut.json`:
23
+ // the saved cut (the one survey and scan reuse), the previous saved cut (so a
24
+ // re-cut can be compared and undone), and at most one draft. The ports the
25
+ // scan shares (the checkout, the state home, the case runner) are
26
+ // scanports.ts's.
29
27
  import { isSampleable } from "./survey.js";
30
28
  import type { MemoryStore } from "../memory/store.js";
31
- import { isSweepSkipped } from "@quickchatai/pi-quickreview/src/heuristics.js";
32
- import {
33
- repoFacts,
34
- unitCandidates,
35
- vendoredRoots,
36
- type RepoFacts,
37
- type UnitCandidate,
38
- } from "./facts.js";
29
+ import { repoFacts, type RepoFacts } from "./facts.js";
39
30
  import {
40
31
  SurveyRunner,
41
32
  type SurveyInputs,
@@ -79,96 +70,49 @@ import {
79
70
  import type { ProfileCheck } from "./profile.js";
80
71
  import {
81
72
  DEFAULT_CASE_BUDGET,
82
- applyOps,
83
73
  assign,
84
- checkCut,
85
- normalizeCut,
86
- splitUnit,
87
74
  type CaseBudget,
88
75
  type CasePlan,
89
76
  type UnitCut,
90
77
  } from "./unitcut.js";
78
+ import type {
79
+ CaseRunner,
80
+ Refusal,
81
+ RepoReader,
82
+ StateFs,
83
+ } from "@quickchatai/pi-quickreview/src/scan/scanports.js";
84
+ import {
85
+ UnitCutStore,
86
+ candidateCut,
87
+ type CutStatus,
88
+ type CutView,
89
+ type SavedCut,
90
+ type UnitCutFile,
91
+ } from "@quickchatai/pi-quickreview/src/scan/unitcutstore.js";
91
92
 
92
93
  // ---- ports -------------------------------------------------------------------
93
94
 
94
- /** The reviewed checkout, read-only (the companion mounts it `:ro`). Every
95
- * method is total: an unreadable checkout answers null, never throws. */
96
- export interface RepoReader {
97
- /** Where the checkout is, for messages. */
98
- readonly location: string;
99
- /** Repo-relative paths of every tracked file, or null when there is no
100
- * readable checkout. */
101
- listFiles(): string[] | null;
102
- /** A file's text, or null (missing, binary, over the reader's size cap). */
103
- read(rel: string): string | null;
104
- /** A file's line count (0 for an empty file), or null when unreadable. */
105
- lines(rel: string): number | null;
106
- /** The checked-out commit, or null when it cannot be read. */
107
- head(): string | null;
108
- /** Commits touching each file over the recent history — the survey's
109
- * "most-changed" sample. Optional and total: absent or null when
110
- * the history cannot be read, which only empties that one reason. */
111
- changeCounts?(): Record<string, number> | null;
112
- }
113
-
114
- /** The filesystem rooted at the install's state home — PC-only, never shipped.
115
- * Paths are relative to that root; the implementation refuses any that would
116
- * leave it. The same shape roots the config home (`SetupPorts.config`). */
117
- export interface StateFs {
118
- readonly location: string;
119
- /** The file's text, or null when it does not exist. Throws only when it
120
- * exists and cannot be read — the caller reports that, never overwrites. */
121
- read(rel: string): string | null;
122
- /** tmp+rename atomic write; creates parent directories. */
123
- write(rel: string, text: string): void;
124
- }
125
-
126
- /** One case: a unit (or part of one) handed to background pi subagents inside
127
- * the companion. */
128
- export interface CaseSpec {
129
- id: string;
130
- unit: string;
131
- agent: string;
132
- files: string[];
133
- prompt: string;
134
- }
135
-
136
- /** How a case ended: the lens's final answer and what it cost (null when
137
- * the runner could not tell), or why there is none. A failed or aborted case
138
- * may still have spent money: `cost_usd` on a failure is what it spent before
139
- * it ended (0 when it never started, null or absent when the runner cannot
140
- * tell) — a caller holding a budget counts it. */
141
- export type CaseOutcome =
142
- | {
143
- ok: true;
144
- output: string;
145
- cost_usd: number | null;
146
- /** The files the case's file-read tool calls opened (`read`, and
147
- * `grep` given a `path`), in call order, each resolved to the absolute
148
- * path the call opened (against the case's working directory, which is
149
- * not the scanned tree) — `[]` when it read nothing. Absent when the
150
- * runner cannot tell, which is unknown, never "read nothing". The scan
151
- * judges a lens that read none of its focus files as no reading at all. */
152
- reads?: string[];
153
- /** Where the runner kept the case's event stream, relative to its
154
- * transcript directory; absent when it kept none. */
155
- transcript?: string;
156
- }
157
- | { ok: false; reason: string; cost_usd?: number | null; transcript?: string };
158
-
159
- /** Runs cases as background subagents (survey, scan). The
160
- * engine decides what runs when and how many at once; the runner runs one
161
- * case to its end. Resolves for every outcome — a failed or aborted case is
162
- * `{ok:false}`, never a rejection. `onCost`, when given, hears the case's
163
- * running cost each time the runner learns more of it, so a caller with a
164
- * hard cap can abort a case mid-flight rather than after the fact. */
165
- export interface CaseRunner {
166
- run(
167
- spec: CaseSpec,
168
- signal: AbortSignal,
169
- onCost?: (spentSoFar: number) => void,
170
- ): Promise<CaseOutcome>;
171
- }
95
+ // The checkout, the state home and the case runner: the scan's own ports,
96
+ // shared with it (scanports.ts).
97
+ export type {
98
+ CaseOutcome,
99
+ CaseRunner,
100
+ CaseSpec,
101
+ Refusal,
102
+ RepoReader,
103
+ StateFs,
104
+ } from "@quickchatai/pi-quickreview/src/scan/scanports.js";
105
+ // The stored unit cut (unitcutstore.ts), under the names setup has always used.
106
+ export {
107
+ UNIT_CUT_FILE,
108
+ UNIT_CUT_VERSION,
109
+ type CutStatus,
110
+ type CutView,
111
+ type DraftCut,
112
+ type SavedCut,
113
+ type UnitCutFile,
114
+ type UnitSummary,
115
+ } from "@quickchatai/pi-quickreview/src/scan/unitcutstore.js";
172
116
 
173
117
  export interface TrackerTicket {
174
118
  title: string;
@@ -241,124 +185,24 @@ export interface SetupOptions {
241
185
  surveyConcurrency?: number;
242
186
  }
243
187
 
244
- // ---- the persisted file ------------------------------------------------------
245
-
246
- export const UNIT_CUT_FILE = "setup/unit-cut.json";
247
- export const UNIT_CUT_VERSION = 1;
248
-
249
- export interface SavedCut extends UnitCut {
250
- saved_at: string;
251
- /** The commit the cut was checked against, when readable. */
252
- head: string | null;
253
- /** How many files the cut covered at save — a later listing that differs is
254
- * how a stale cut shows itself. */
255
- files: number;
256
- }
257
-
258
- export interface DraftCut extends UnitCut {
259
- updated_at: string;
260
- /** `candidates` = the deterministic guess taken as-is; `companion` = a
261
- * proposal the companion wrote; `adjusted` = either, after operator edits. */
262
- source: "candidates" | "companion" | "adjusted";
263
- }
264
-
265
- export interface UnitCutFile {
266
- version: number;
267
- saved: SavedCut | null;
268
- previous: SavedCut | null;
269
- draft: DraftCut | null;
270
- }
271
-
272
188
  // ---- results -----------------------------------------------------------------
273
189
 
274
- export type Refusal = { ok: false; errors: string[] };
275
-
276
- export interface UnitSummary {
277
- name: string;
278
- kind: string;
279
- roots: string[];
280
- note: string;
281
- files: number | null;
282
- /** Files a pass would actually read (lockfiles, binaries, prose skipped). */
283
- reviewable: number | null;
284
- lines: number | null;
285
- /** One per case; a unit that fits is one case under its own name. */
286
- cases: Array<{ id: string; files: number; lines: number; over_budget: boolean }> | null;
287
- }
288
-
289
- export interface CutView {
290
- units: UnitSummary[];
291
- ignored: string[];
292
- ignored_files: number | null;
293
- unassigned: string[] | null;
294
- /** Structural refusals — should never appear on a stored cut; shown if a
295
- * hand-edited file carries one. */
296
- errors: string[];
297
- /** What save would refuse for (a draft may carry these; a saved cut that
298
- * shows one has gone stale against the repo). */
299
- blocking_save: string[];
300
- }
301
-
302
- export interface CutStatus {
303
- ok: boolean;
304
- /** Why the file or the repo could not be read — the rest is then partial. */
305
- problems: string[];
306
- /** The cut file exists but could not be read: `saved`/`draft` are then
307
- * unknown, not absent. */
308
- cut_file_unreadable: boolean;
309
- saved: (CutView & { saved_at: string; head: string | null; files_at_save: number }) | null;
310
- draft: (CutView & { updated_at: string; source: DraftCut["source"] }) | null;
311
- has_previous: boolean;
312
- repo_files: number | null;
313
- head: string | null;
314
- }
315
-
316
190
  export type FactsResult = { ok: true; head: string | null; facts: RepoFacts } | Refusal;
317
191
 
318
- /** Why a stored cut is not one this version can read, or null when it is (or
319
- * is absent). Structure only — the partition itself is `checkCut`'s. */
320
- function cutShapeError(cut: unknown): string | null {
321
- if (cut === null) return null;
322
- if (typeof cut !== "object") return "is not an object";
323
- const c = cut as { units?: unknown; ignored?: unknown };
324
- const strings = (v: unknown): boolean =>
325
- Array.isArray(v) && v.every((x) => typeof x === "string");
326
- if (!Array.isArray(c.units)) return "has no `units` array";
327
- for (const [i, u] of c.units.entries()) {
328
- const unit = (u ?? {}) as { name?: unknown; roots?: unknown; kind?: unknown; note?: unknown };
329
- if (
330
- typeof unit.name !== "string" ||
331
- !strings(unit.roots) ||
332
- typeof unit.kind !== "string" ||
333
- typeof unit.note !== "string"
334
- ) {
335
- return `units[${i}] is not {name, roots, kind, note}`;
336
- }
337
- }
338
- if (!strings(c.ignored)) return "has no `ignored` array of paths";
339
- return null;
340
- }
341
-
342
192
  // ---- engine ------------------------------------------------------------------
343
193
 
344
- /** The cut setup offers when none is saved: the candidates as units, the
345
- * vendored roots ignored. */
346
- function candidateCut(candidates: readonly UnitCandidate[], files: readonly string[]): UnitCut {
347
- return {
348
- units: candidates.map((c) => ({ name: c.name, roots: c.roots, kind: c.kind, note: c.reason })),
349
- ignored: vendoredRoots(files),
350
- };
351
- }
352
-
353
194
  export class SetupEngine {
354
195
  private readonly budget: CaseBudget;
355
196
  private readonly surveyRun: SurveyRunner;
197
+ /** The unit cut: its file, its views and its three verbs. */
198
+ private readonly cut: UnitCutStore;
356
199
 
357
200
  constructor(
358
201
  private readonly ports: SetupPorts,
359
202
  opts: SetupOptions = {},
360
203
  ) {
361
204
  this.budget = opts.caseBudget ?? DEFAULT_CASE_BUDGET;
205
+ this.cut = new UnitCutStore(ports, this.budget);
362
206
  this.surveyRun = new SurveyRunner(ports, () => this.surveyInputs(), {
363
207
  repoRoot: ports.repo.location,
364
208
  ...(opts.surveyConcurrency !== undefined ? { concurrency: opts.surveyConcurrency } : {}),
@@ -366,17 +210,11 @@ export class SetupEngine {
366
210
  }
367
211
 
368
212
  private files(): string[] | null {
369
- const listed = this.ports.repo.listFiles();
370
- return listed === null ? null : [...listed].sort();
213
+ return this.cut.files();
371
214
  }
372
215
 
373
216
  private noRepo(): Refusal {
374
- return {
375
- ok: false,
376
- errors: [
377
- `no readable checkout at ${this.ports.repo.location} — is the reviewed repo mounted?`,
378
- ],
379
- };
217
+ return this.cut.noRepo();
380
218
  }
381
219
 
382
220
  /** The deterministic repo facts. Recomputed on every call — they are cheap
@@ -391,325 +229,53 @@ export class SetupEngine {
391
229
  };
392
230
  }
393
231
 
394
- // -- the file --
232
+ // -- the unit cut (unitcutstore.ts) --
395
233
 
396
234
  private load(): { file: UnitCutFile; error: string | null } {
397
- const empty: UnitCutFile = {
398
- version: UNIT_CUT_VERSION,
399
- saved: null,
400
- previous: null,
401
- draft: null,
402
- };
403
- let text: string | null;
404
- try {
405
- text = this.ports.state.read(UNIT_CUT_FILE);
406
- } catch (err) {
407
- return {
408
- file: empty,
409
- error: `${UNIT_CUT_FILE} in ${this.ports.state.location} cannot be read: ${String(err)}`,
410
- };
411
- }
412
- if (text === null) return { file: empty, error: null };
413
- try {
414
- const raw = JSON.parse(text) as Partial<UnitCutFile>;
415
- if (typeof raw !== "object" || raw === null || raw.version !== UNIT_CUT_VERSION) {
416
- throw new Error(
417
- `unknown version ${JSON.stringify((raw as { version?: unknown } | null)?.version)}`,
418
- );
419
- }
420
- const file: UnitCutFile = {
421
- version: UNIT_CUT_VERSION,
422
- saved: raw.saved ?? null,
423
- previous: raw.previous ?? null,
424
- draft: raw.draft ?? null,
425
- };
426
- // A hand-edited file must be reported, never crash a view: every later
427
- // reader (assign, splitUnit) takes the shape on trust.
428
- for (const key of ["saved", "previous", "draft"] as const) {
429
- const bad = cutShapeError(file[key]);
430
- if (bad !== null) throw new Error(`\`${key}\` ${bad}`);
431
- }
432
- return { file, error: null };
433
- } catch (err) {
434
- return {
435
- file: empty,
436
- error:
437
- `${UNIT_CUT_FILE} in ${this.ports.state.location} is not a unit cut this version reads (${String(err)}) — ` +
438
- "nothing will overwrite it; move it aside to start over",
439
- };
440
- }
441
- }
442
-
443
- private store(file: UnitCutFile): void {
444
- this.ports.state.write(UNIT_CUT_FILE, `${JSON.stringify(file, null, 2)}\n`);
445
- }
446
-
447
- /** Load for a write: an unreadable file refuses rather than being replaced. */
448
- private loadForWrite(): { file: UnitCutFile } | Refusal {
449
- const { file, error } = this.load();
450
- return error === null ? { file } : { ok: false, errors: [error] };
451
- }
452
-
453
- // -- views --
454
-
455
- /** A unit's case plans over its reviewable (not sweep-skipped) files. */
456
- private unitCases(
457
- name: string,
458
- owned: readonly string[],
459
- lines: (p: string) => number,
460
- ): CasePlan[] {
461
- return splitUnit(
462
- name,
463
- owned.filter((f) => !isSweepSkipped(f)),
464
- lines,
465
- this.budget,
466
- );
235
+ return this.cut.load();
467
236
  }
468
237
 
469
238
  private view(cut: UnitCut, files: readonly string[], lines: (p: string) => number): CutView {
470
- const { errors, blocking_save } = checkCut(cut, files);
471
- // A cut with structural errors (a hand-edited file) still gets a
472
- // best-effort view, with the errors beside it.
473
- const a = assign(cut, files);
474
- const sizeErrors: string[] = [];
475
- const units: UnitSummary[] = cut.units.map((u) => {
476
- const owned = a.byUnit.get(u.name) ?? [];
477
- const reviewable = owned.filter((f) => !isSweepSkipped(f));
478
- let cases: CasePlan[] = [];
479
- let unavailable = false;
480
- try {
481
- cases = this.unitCases(u.name, owned, lines);
482
- } catch (err) {
483
- unavailable = true;
484
- sizeErrors.push(String(err));
485
- }
486
- return {
487
- name: u.name,
488
- kind: u.kind,
489
- roots: u.roots,
490
- note: u.note,
491
- files: owned.length,
492
- reviewable: reviewable.length,
493
- lines: unavailable ? null : cases.reduce((n, c) => n + c.lines, 0),
494
- cases: unavailable
495
- ? null
496
- : cases.map((c) => ({
497
- id: c.id,
498
- files: c.files.length,
499
- lines: c.lines,
500
- over_budget: c.over_budget,
501
- })),
502
- };
503
- });
504
- return {
505
- units,
506
- ignored: cut.ignored,
507
- ignored_files: a.ignored.length,
508
- unassigned: a.unassigned,
509
- errors: [...errors, ...sizeErrors],
510
- blocking_save,
511
- };
239
+ return this.cut.view(cut, files, lines);
512
240
  }
513
241
 
514
242
  private lineCounter(): (p: string) => number {
515
- const cache = new Map<string, number>();
516
- return (p) => {
517
- let n = cache.get(p);
518
- if (n === undefined) {
519
- const measured = this.ports.repo.lines(p);
520
- if (measured === null)
521
- throw new Error(
522
- `line count UNKNOWN for ${p} — unreadable, binary or too large; no case or price can be inferred`,
523
- );
524
- n = measured;
525
- cache.set(p, n);
526
- }
527
- return n;
528
- };
243
+ return this.cut.lineCounter();
529
244
  }
530
245
 
531
246
  /** The saved cut and the draft, each re-checked against the repo as it is
532
247
  * now — so a saved cut the repo has outgrown (a new top-level directory no
533
248
  * unit claims) shows up here instead of being silently skipped later. */
534
249
  status(): CutStatus {
535
- const { file, error } = this.load();
536
- const problems = error === null ? [] : [error];
537
- const files = this.files();
538
- if (files === null) problems.push(this.noRepo().errors[0]!);
539
- const head = files === null ? null : this.ports.repo.head();
540
- const lines = this.lineCounter();
541
- const see = (cut: UnitCut): CutView =>
542
- files === null
543
- ? {
544
- units: cut.units.map((u) => ({
545
- ...u,
546
- files: null,
547
- reviewable: null,
548
- lines: null,
549
- cases: null,
550
- })),
551
- ignored: cut.ignored,
552
- ignored_files: null,
553
- unassigned: null,
554
- errors: [],
555
- blocking_save: [],
556
- }
557
- : this.view(cut, files, lines);
558
- return {
559
- ok: problems.length === 0,
560
- problems,
561
- cut_file_unreadable: error !== null,
562
- saved: file.saved
563
- ? {
564
- ...see(file.saved),
565
- saved_at: file.saved.saved_at,
566
- head: file.saved.head,
567
- files_at_save: file.saved.files,
568
- }
569
- : null,
570
- draft: file.draft
571
- ? { ...see(file.draft), updated_at: file.draft.updated_at, source: file.draft.source }
572
- : null,
573
- has_previous: file.previous !== null,
574
- repo_files: files?.length ?? null,
575
- head,
576
- };
250
+ return this.cut.status();
577
251
  }
578
252
 
579
- // -- the three verbs --
580
-
581
- /**
582
- * Store a proposal as the draft, replacing any earlier draft (never the
583
- * saved cut). `raw` is the companion's proposal — `{units, ignored?}` — or
584
- * undefined to take the deterministic candidates as they are (vendored
585
- * trees ignored). Refused when it is structurally wrong; save-blocking
586
- * conditions are allowed in a draft and reported.
587
- */
253
+ /** Store a proposal as the draft (unitcutstore.ts `propose`). */
588
254
  propose(raw?: unknown): { ok: true; draft: CutView } | Refusal {
589
- const files = this.files();
590
- if (files === null) return this.noRepo();
591
- const loaded = this.loadForWrite();
592
- if ("ok" in loaded) return loaded;
593
- let cut: UnitCut;
594
- let source: DraftCut["source"];
595
- if (raw === undefined || raw === null || (typeof raw === "object" && !("units" in raw))) {
596
- cut = candidateCut(unitCandidates(files), files);
597
- source = "candidates";
598
- } else {
599
- const n = normalizeCut(raw);
600
- if (!n.ok) return n;
601
- cut = n.cut;
602
- source = "companion";
603
- }
604
- const problems = checkCut(cut, files);
605
- if (problems.errors.length > 0) return { ok: false, errors: problems.errors };
606
- loaded.file.draft = { ...cut, updated_at: this.ports.clock().toISOString(), source };
607
- this.store(loaded.file);
608
- return { ok: true, draft: this.view(cut, files, this.lineCounter()) };
609
- }
610
-
611
- /**
612
- * Apply the operator's corrections to the draft — starting one from the
613
- * saved cut when there is no draft — all-or-nothing: a refused op, or a
614
- * result that is structurally wrong, leaves the stored draft as it was.
615
- */
255
+ return this.cut.propose(raw);
256
+ }
257
+
258
+ /** Apply the operator's corrections to the draft (unitcutstore.ts `adjust`). */
616
259
  adjust(ops: unknown): { ok: true; draft: CutView } | Refusal {
617
- if (!Array.isArray(ops) || ops.length === 0) {
618
- return { ok: false, errors: ["`ops` must be a non-empty array of adjust operations"] };
619
- }
620
- const files = this.files();
621
- if (files === null) return this.noRepo();
622
- const loaded = this.loadForWrite();
623
- if ("ok" in loaded) return loaded;
624
- const base = loaded.file.draft ?? loaded.file.saved;
625
- if (!base)
626
- return { ok: false, errors: ["there is no unit cut to adjust yet — propose one first"] };
627
- const applied = applyOps({ units: base.units, ignored: base.ignored }, ops);
628
- if (!applied.ok) return applied;
629
- const problems = checkCut(applied.cut, files);
630
- if (problems.errors.length > 0) return { ok: false, errors: problems.errors };
631
- loaded.file.draft = {
632
- ...applied.cut,
633
- updated_at: this.ports.clock().toISOString(),
634
- source: "adjusted",
635
- };
636
- this.store(loaded.file);
637
- return { ok: true, draft: this.view(applied.cut, files, this.lineCounter()) };
260
+ return this.cut.adjust(ops);
638
261
  }
639
262
 
640
- /**
641
- * Promote the draft to the unit cut. Refused unless it is a complete
642
- * partition of the repo as it is now — every file in exactly one unit or
643
- * deliberately ignored, no unit left empty. The cut it replaces is kept as
644
- * `previous`.
645
- */
263
+ /** Promote the draft to the unit cut (unitcutstore.ts `save`). */
646
264
  save(): { ok: true; saved: CutView } | Refusal {
647
- const files = this.files();
648
- if (files === null) return this.noRepo();
649
- const loaded = this.loadForWrite();
650
- if ("ok" in loaded) return loaded;
651
- const draft = loaded.file.draft;
652
- if (!draft) {
653
- return {
654
- ok: false,
655
- errors: [
656
- loaded.file.saved
657
- ? "no draft to save — the saved cut is already in use; adjust it to start a draft"
658
- : "no draft to save — propose a unit cut first",
659
- ],
660
- };
661
- }
662
- const cut: UnitCut = { units: draft.units, ignored: draft.ignored };
663
- const problems = checkCut(cut, files);
664
- if (problems.errors.length > 0 || problems.blocking_save.length > 0) {
665
- return { ok: false, errors: [...problems.errors, ...problems.blocking_save] };
666
- }
667
- loaded.file.previous = loaded.file.saved;
668
- loaded.file.saved = {
669
- ...cut,
670
- saved_at: this.ports.clock().toISOString(),
671
- head: this.ports.repo.head(),
672
- files: files.length,
673
- };
674
- loaded.file.draft = null;
675
- this.store(loaded.file);
676
- return { ok: true, saved: this.view(cut, files, this.lineCounter()) };
265
+ return this.cut.save();
677
266
  }
678
267
 
679
- // -- reuse --
680
-
681
268
  /** The saved cut, for every later survey and scan; null when none is saved
682
269
  * (or the file cannot be read — `status()` says which). */
683
270
  savedCut(): SavedCut | null {
684
- return this.load().file.saved;
271
+ return this.cut.savedCut();
685
272
  }
686
273
 
687
- /**
688
- * The saved cut as cases: every unit's reviewable files (optionally only
689
- * those `include` keeps — a scan's stage 1 drops tests and migrations),
690
- * split to fit the case budget, each case under its unit's name. Refused
691
- * when there is no saved cut: a survey or scan never runs on a guess.
692
- */
274
+ /** The saved cut as cases (unitcutstore.ts `cases`). */
693
275
  cases(
694
276
  include: (path: string) => boolean = () => true,
695
277
  ): { ok: true; cases: CasePlan[] } | Refusal {
696
- const files = this.files();
697
- if (files === null) return this.noRepo();
698
- const { file, error } = this.load();
699
- if (error !== null) return { ok: false, errors: [error] };
700
- if (!file.saved)
701
- return { ok: false, errors: ["no saved unit cut — propose, adjust and save one first"] };
702
- const a = assign(file.saved, files);
703
- const lines = this.lineCounter();
704
- const cases: CasePlan[] = [];
705
- try {
706
- for (const u of file.saved.units) {
707
- cases.push(...this.unitCases(u.name, (a.byUnit.get(u.name) ?? []).filter(include), lines));
708
- }
709
- } catch (err) {
710
- return { ok: false, errors: [String(err)] };
711
- }
712
- return { ok: true, cases };
278
+ return this.cut.cases(include);
713
279
  }
714
280
 
715
281
  // -- review history (src/setup/history.ts) --