@quickchatai/quickreview 1.2.0 → 1.3.2

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 (350) hide show
  1. package/README.md +116 -53
  2. package/bin/qcr.js +1 -1
  3. package/config.example.toml +25 -7
  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/selfimprove.js +18 -0
  11. package/dist/extensions/cli/selfimprove.js.map +1 -0
  12. package/dist/extensions/home.js +350 -0
  13. package/dist/extensions/home.js.map +1 -0
  14. package/dist/extensions/login-route.js +1 -1
  15. package/dist/extensions/login-route.js.map +1 -1
  16. package/dist/extensions/onboarding.js +8 -7
  17. package/dist/extensions/onboarding.js.map +1 -1
  18. package/dist/extensions/selfimprove.js +49 -0
  19. package/dist/extensions/selfimprove.js.map +1 -0
  20. package/dist/extensions/setup-scan.js +23 -592
  21. package/dist/extensions/setup-scan.js.map +1 -1
  22. package/dist/extensions/setup.js +2 -47
  23. package/dist/extensions/setup.js.map +1 -1
  24. package/dist/extensions/vendor/openviking-pi/index.js +4 -0
  25. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -1
  26. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +3 -1
  27. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -1
  28. package/dist/extensions/vendor/openviking-pi/sync.js +81 -0
  29. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -1
  30. package/dist/src/archive/restore.js +18 -15
  31. package/dist/src/archive/restore.js.map +1 -1
  32. package/dist/src/botworker.js +13 -2
  33. package/dist/src/botworker.js.map +1 -1
  34. package/dist/src/channelfs.js +3 -26
  35. package/dist/src/channelfs.js.map +1 -1
  36. package/dist/src/cli.js +14 -50
  37. package/dist/src/cli.js.map +1 -1
  38. package/dist/src/config.js +16 -0
  39. package/dist/src/config.js.map +1 -1
  40. package/dist/src/daemon.js +33 -4
  41. package/dist/src/daemon.js.map +1 -1
  42. package/dist/src/debuground.js +1 -0
  43. package/dist/src/debuground.js.map +1 -1
  44. package/dist/src/debugroundpi.js +1 -0
  45. package/dist/src/debugroundpi.js.map +1 -1
  46. package/dist/src/engine/caserunner-sdk.js +15 -251
  47. package/dist/src/engine/caserunner-sdk.js.map +1 -1
  48. package/dist/src/home/adapters.js +349 -0
  49. package/dist/src/home/adapters.js.map +1 -0
  50. package/dist/src/home/advanced.js +318 -0
  51. package/dist/src/home/advanced.js.map +1 -0
  52. package/dist/src/home/advancedtools.js +57 -0
  53. package/dist/src/home/advancedtools.js.map +1 -0
  54. package/dist/src/home/autoreview.js +325 -0
  55. package/dist/src/home/autoreview.js.map +1 -0
  56. package/dist/src/home/daemonstatus.js +32 -0
  57. package/dist/src/home/daemonstatus.js.map +1 -0
  58. package/dist/src/home/devicelogin.js +61 -0
  59. package/dist/src/home/devicelogin.js.map +1 -0
  60. package/dist/src/home/launch.js +202 -0
  61. package/dist/src/home/launch.js.map +1 -0
  62. package/dist/src/home/loginowner.js +205 -0
  63. package/dist/src/home/loginowner.js.map +1 -0
  64. package/dist/src/home/memory.js +122 -0
  65. package/dist/src/home/memory.js.map +1 -0
  66. package/dist/src/home/model.js +811 -0
  67. package/dist/src/home/model.js.map +1 -0
  68. package/dist/src/home/panel.js +520 -0
  69. package/dist/src/home/panel.js.map +1 -0
  70. package/dist/src/home/ports.js +19 -0
  71. package/dist/src/home/ports.js.map +1 -0
  72. package/dist/src/home/review.js +163 -0
  73. package/dist/src/home/review.js.map +1 -0
  74. package/dist/src/home/reviews.js +478 -0
  75. package/dist/src/home/reviews.js.map +1 -0
  76. package/dist/src/home/setup.js +507 -0
  77. package/dist/src/home/setup.js.map +1 -0
  78. package/dist/src/home/setupadapters.js +214 -0
  79. package/dist/src/home/setupadapters.js.map +1 -0
  80. package/dist/src/home/setupcard.js +199 -0
  81. package/dist/src/home/setupcard.js.map +1 -0
  82. package/dist/src/home/setuptools.js +45 -0
  83. package/dist/src/home/setuptools.js.map +1 -0
  84. package/dist/src/home/spend.js +160 -0
  85. package/dist/src/home/spend.js.map +1 -0
  86. package/dist/src/home/switches.js +209 -0
  87. package/dist/src/home/switches.js.map +1 -0
  88. package/dist/src/home/switchon.js +439 -0
  89. package/dist/src/home/switchon.js.map +1 -0
  90. package/dist/src/home/toolnames.js +41 -0
  91. package/dist/src/home/toolnames.js.map +1 -0
  92. package/dist/src/home/tune.js +341 -0
  93. package/dist/src/home/tune.js.map +1 -0
  94. package/dist/src/home/words.js +7 -0
  95. package/dist/src/home/words.js.map +1 -0
  96. package/dist/src/homes.js +14 -0
  97. package/dist/src/homes.js.map +1 -1
  98. package/dist/src/install.js +1 -1
  99. package/dist/src/install.js.map +1 -1
  100. package/dist/src/ipc/workerspec.js +8 -0
  101. package/dist/src/ipc/workerspec.js.map +1 -1
  102. package/dist/src/ledgercheck.js +39 -10
  103. package/dist/src/ledgercheck.js.map +1 -1
  104. package/dist/src/live/publish.js +1 -1
  105. package/dist/src/live/publish.js.map +1 -1
  106. package/dist/src/livecli.js +1 -1
  107. package/dist/src/livecli.js.map +1 -1
  108. package/dist/src/loginrefresh/handover.js +121 -0
  109. package/dist/src/loginrefresh/handover.js.map +1 -0
  110. package/dist/src/loginrefresh/lock.js +54 -3
  111. package/dist/src/loginrefresh/lock.js.map +1 -1
  112. package/dist/src/loginrefresh/refresher.js +33 -5
  113. package/dist/src/loginrefresh/refresher.js.map +1 -1
  114. package/dist/src/loginrefresh/supervisor.js +18 -1
  115. package/dist/src/loginrefresh/supervisor.js.map +1 -1
  116. package/dist/src/npmverbs.js +42 -12
  117. package/dist/src/npmverbs.js.map +1 -1
  118. package/dist/src/onboard/choices.js +6 -3
  119. package/dist/src/onboard/choices.js.map +1 -1
  120. package/dist/src/onboard/engine.js +33 -292
  121. package/dist/src/onboard/engine.js.map +1 -1
  122. package/dist/src/onboard/steps/bringup.js +3 -2
  123. package/dist/src/onboard/steps/bringup.js.map +1 -1
  124. package/dist/src/onboard/steps/configfill.js +18 -7
  125. package/dist/src/onboard/steps/configfill.js.map +1 -1
  126. package/dist/src/onboard/steps/loginshare.js +213 -0
  127. package/dist/src/onboard/steps/loginshare.js.map +1 -0
  128. package/dist/src/onboard/steps/memory.js +14 -2
  129. package/dist/src/onboard/steps/memory.js.map +1 -1
  130. package/dist/src/onboard/steps/repoconfig.js +72 -0
  131. package/dist/src/onboard/steps/repoconfig.js.map +1 -0
  132. package/dist/src/onboard/toolnames.js +2 -2
  133. package/dist/src/onboard/toolnames.js.map +1 -1
  134. package/dist/src/piagentdir.js +13 -0
  135. package/dist/src/piagentdir.js.map +1 -0
  136. package/dist/src/pilogin.js +31 -16
  137. package/dist/src/pilogin.js.map +1 -1
  138. package/dist/src/qcrbin.js +16 -11
  139. package/dist/src/qcrbin.js.map +1 -1
  140. package/dist/src/reviewargs.js +122 -0
  141. package/dist/src/reviewargs.js.map +1 -0
  142. package/dist/src/reviewcli.js +7 -161
  143. package/dist/src/reviewcli.js.map +1 -1
  144. package/dist/src/reviewdetach.js +76 -11
  145. package/dist/src/reviewdetach.js.map +1 -1
  146. package/dist/src/scancli.js +39 -691
  147. package/dist/src/scancli.js.map +1 -1
  148. package/dist/src/selfimprove.js +66 -0
  149. package/dist/src/selfimprove.js.map +1 -0
  150. package/dist/src/session-command-supervisor.js +38 -9
  151. package/dist/src/session-command-supervisor.js.map +1 -1
  152. package/dist/src/setup/adapters.js +5 -223
  153. package/dist/src/setup/adapters.js.map +1 -1
  154. package/dist/src/setup/caserunner.js +15 -249
  155. package/dist/src/setup/caserunner.js.map +1 -1
  156. package/dist/src/setup/engine.js +32 -341
  157. package/dist/src/setup/engine.js.map +1 -1
  158. package/dist/src/setup/facts.js +3 -592
  159. package/dist/src/setup/facts.js.map +1 -1
  160. package/dist/src/setup/findings.js +3 -947
  161. package/dist/src/setup/findings.js.map +1 -1
  162. package/dist/src/setup/presentation.js +3 -8
  163. package/dist/src/setup/presentation.js.map +1 -1
  164. package/dist/src/setup/scan.js +3 -724
  165. package/dist/src/setup/scan.js.map +1 -1
  166. package/dist/src/setup/scanbuild.js +9 -92
  167. package/dist/src/setup/scanbuild.js.map +1 -1
  168. package/dist/src/setup/scanlease.js +3 -250
  169. package/dist/src/setup/scanlease.js.map +1 -1
  170. package/dist/src/setup/scanreport.js +3 -288
  171. package/dist/src/setup/scanreport.js.map +1 -1
  172. package/dist/src/setup/scanrun.js +3 -1732
  173. package/dist/src/setup/scanrun.js.map +1 -1
  174. package/dist/src/setup/scantree.js +3 -256
  175. package/dist/src/setup/scantree.js.map +1 -1
  176. package/dist/src/setup/stage2request.js +3 -258
  177. package/dist/src/setup/stage2request.js.map +1 -1
  178. package/dist/src/setup/unitcut.js +3 -436
  179. package/dist/src/setup/unitcut.js.map +1 -1
  180. package/dist/src/state.js +75 -9
  181. package/dist/src/state.js.map +1 -1
  182. package/dist/src/takeover.js +1 -0
  183. package/dist/src/takeover.js.map +1 -1
  184. package/dist/src/takeoverpi.js +2 -1
  185. package/dist/src/takeoverpi.js.map +1 -1
  186. package/dist/src/tui/alerts.js +4 -1
  187. package/dist/src/tui/alerts.js.map +1 -1
  188. package/dist/src/tui/board.js +2 -2
  189. package/dist/src/tui/board.js.map +1 -1
  190. package/dist/src/tui/companion.js +2 -1
  191. package/dist/src/tui/companion.js.map +1 -1
  192. package/dist/src/tui/deploytab.js +2 -2
  193. package/dist/src/tui/deploytab.js.map +1 -1
  194. package/dist/src/tui/index.js +9 -6
  195. package/dist/src/tui/index.js.map +1 -1
  196. package/dist/src/tui/memory.js +3 -16
  197. package/dist/src/tui/memory.js.map +1 -1
  198. package/dist/src/tui/memoryurl.js +22 -0
  199. package/dist/src/tui/memoryurl.js.map +1 -0
  200. package/dist/src/tui/remote.js +1 -1
  201. package/dist/src/tui/remote.js.map +1 -1
  202. package/dist/src/tui/route.js +2 -2
  203. package/dist/src/tui/route.js.map +1 -1
  204. package/dist/src/tui/setupstate.js +8 -5
  205. package/dist/src/tui/setupstate.js.map +1 -1
  206. package/dist/src/tui/sshtriage.js +1 -1
  207. package/dist/src/tui/sshtriage.js.map +1 -1
  208. package/dist/src/verdicts.js +12 -0
  209. package/dist/src/verdicts.js.map +1 -1
  210. package/dist/src/wire.js +9 -4
  211. package/dist/src/wire.js.map +1 -1
  212. package/dist/src/workerbreaker.js +32 -5
  213. package/dist/src/workerbreaker.js.map +1 -1
  214. package/dist/src/workerworld.js +4 -0
  215. package/dist/src/workerworld.js.map +1 -1
  216. package/docker/README.md +1 -1
  217. package/docker/entrypoint.sh +40 -6
  218. package/docker-compose.yml +16 -1
  219. package/extensions/cli/home.ts +14 -0
  220. package/extensions/cli/selfimprove.ts +18 -0
  221. package/extensions/home.ts +484 -0
  222. package/extensions/login-route.ts +1 -1
  223. package/extensions/onboarding.ts +8 -7
  224. package/extensions/selfimprove.ts +65 -0
  225. package/extensions/setup-scan.ts +79 -766
  226. package/extensions/setup.ts +14 -70
  227. package/extensions/vendor/openviking-pi/NOTICE +5 -0
  228. package/extensions/vendor/openviking-pi/README.md +39 -5
  229. package/extensions/vendor/openviking-pi/index.ts +4 -0
  230. package/extensions/vendor/openviking-pi/patches/0014-one-turn-sync-in-flight.patch +239 -0
  231. package/extensions/vendor/openviking-pi/qcr-hooks.ts +3 -1
  232. package/extensions/vendor/openviking-pi/sync.ts +80 -0
  233. package/extensions/vendor/openviking-pi/upstream.json +2 -1
  234. package/package.json +6 -4
  235. package/prompts/onboarding.md +8 -7
  236. package/schemas/review-result.v1.json +60 -0
  237. package/scripts/deploy.sh +8 -6
  238. package/scripts/qcr-host.sh +28 -5
  239. package/scripts/vm-ghcr-login.sh +7 -4
  240. package/src/archive/restore.ts +18 -21
  241. package/src/botworker.ts +13 -2
  242. package/src/channelfs.ts +3 -26
  243. package/src/cli.ts +13 -54
  244. package/src/config.ts +38 -0
  245. package/src/daemon.ts +34 -4
  246. package/src/debuground.ts +3 -0
  247. package/src/debugroundpi.ts +1 -0
  248. package/src/engine/caserunner-sdk.ts +29 -314
  249. package/src/home/adapters.ts +418 -0
  250. package/src/home/advanced.ts +421 -0
  251. package/src/home/advancedtools.ts +96 -0
  252. package/src/home/autoreview.ts +385 -0
  253. package/src/home/daemonstatus.ts +55 -0
  254. package/src/home/devicelogin.ts +107 -0
  255. package/src/home/launch.ts +257 -0
  256. package/src/home/loginowner.ts +248 -0
  257. package/src/home/memory.ts +176 -0
  258. package/src/home/model.ts +965 -0
  259. package/src/home/panel.ts +666 -0
  260. package/src/home/ports.ts +338 -0
  261. package/src/home/review.ts +206 -0
  262. package/src/home/reviews.ts +565 -0
  263. package/src/home/setup.ts +689 -0
  264. package/src/home/setupadapters.ts +217 -0
  265. package/src/home/setupcard.ts +288 -0
  266. package/src/home/setuptools.ts +57 -0
  267. package/src/home/spend.ts +194 -0
  268. package/src/home/switches.ts +293 -0
  269. package/src/home/switchon.ts +554 -0
  270. package/src/home/toolnames.ts +46 -0
  271. package/src/home/tune.ts +405 -0
  272. package/src/home/words.ts +8 -0
  273. package/src/homes.ts +17 -0
  274. package/src/install.ts +1 -1
  275. package/src/ipc/workerspec.ts +15 -0
  276. package/src/ledgercheck.ts +45 -9
  277. package/src/live/publish.ts +4 -1
  278. package/src/livecli.ts +6 -1
  279. package/src/loginrefresh/handover.ts +138 -0
  280. package/src/loginrefresh/lock.ts +72 -3
  281. package/src/loginrefresh/refresher.ts +34 -5
  282. package/src/loginrefresh/supervisor.ts +25 -2
  283. package/src/npmverbs.ts +51 -11
  284. package/src/onboard/choices.ts +6 -3
  285. package/src/onboard/engine.ts +64 -434
  286. package/src/onboard/steps/bringup.ts +3 -2
  287. package/src/onboard/steps/configfill.ts +23 -7
  288. package/src/onboard/steps/loginshare.ts +258 -0
  289. package/src/onboard/steps/memory.ts +18 -2
  290. package/src/onboard/steps/repoconfig.ts +85 -0
  291. package/src/onboard/toolnames.ts +2 -2
  292. package/src/piagentdir.ts +12 -0
  293. package/src/pilogin.ts +32 -16
  294. package/src/qcrbin.ts +16 -11
  295. package/src/reviewargs.ts +119 -0
  296. package/src/reviewcli.ts +6 -167
  297. package/src/reviewdetach.ts +107 -13
  298. package/src/scancli.ts +75 -826
  299. package/src/selfimprove.ts +90 -0
  300. package/src/session-command-supervisor.ts +41 -8
  301. package/src/setup/adapters.ts +13 -221
  302. package/src/setup/caserunner.ts +34 -273
  303. package/src/setup/engine.ts +62 -496
  304. package/src/setup/facts.ts +3 -627
  305. package/src/setup/findings.ts +3 -1264
  306. package/src/setup/presentation.ts +3 -18
  307. package/src/setup/scan.ts +3 -1033
  308. package/src/setup/scanbuild.ts +20 -114
  309. package/src/setup/scanlease.ts +3 -297
  310. package/src/setup/scanreport.ts +3 -373
  311. package/src/setup/scanrun.ts +3 -2268
  312. package/src/setup/scantree.ts +3 -259
  313. package/src/setup/stage2request.ts +3 -347
  314. package/src/setup/unitcut.ts +3 -523
  315. package/src/state.ts +126 -11
  316. package/src/takeover.ts +5 -0
  317. package/src/takeoverpi.ts +3 -2
  318. package/src/tui/alerts.ts +9 -2
  319. package/src/tui/board.ts +2 -2
  320. package/src/tui/companion.ts +2 -1
  321. package/src/tui/deploytab.ts +2 -2
  322. package/src/tui/index.ts +9 -6
  323. package/src/tui/memory.ts +3 -15
  324. package/src/tui/memoryurl.ts +22 -0
  325. package/src/tui/remote.ts +1 -1
  326. package/src/tui/route.ts +2 -2
  327. package/src/tui/setupstate.ts +20 -16
  328. package/src/tui/sshtriage.ts +1 -1
  329. package/src/verdicts.ts +13 -0
  330. package/src/wire.ts +9 -4
  331. package/src/workerbreaker.ts +41 -5
  332. package/src/workerworld.ts +9 -1
  333. package/dist/src/live/projection.js +0 -450
  334. package/dist/src/live/projection.js.map +0 -1
  335. package/dist/src/live/read.js +0 -60
  336. package/dist/src/live/read.js.map +0 -1
  337. package/dist/src/onboard/launch.js +0 -75
  338. package/dist/src/onboard/launch.js.map +0 -1
  339. package/dist/src/setup/ledger.js +0 -207
  340. package/dist/src/setup/ledger.js.map +0 -1
  341. package/dist/src/setup/stage2.js +0 -617
  342. package/dist/src/setup/stage2.js.map +0 -1
  343. package/dist/src/setup/textsniff.js +0 -89
  344. package/dist/src/setup/textsniff.js.map +0 -1
  345. package/src/live/projection.ts +0 -491
  346. package/src/live/read.ts +0 -86
  347. package/src/onboard/launch.ts +0 -103
  348. package/src/setup/ledger.ts +0 -324
  349. package/src/setup/stage2.ts +0 -926
  350. package/src/setup/textsniff.ts +0 -95
@@ -0,0 +1,119 @@
1
+ // `qcr review`'s arguments: the usage line and the parse of argv into a
2
+ // command, in a module of their own. The terminal verb (src/reviewcli.ts) and
3
+ // the detached registry (src/reviewdetach.ts) both read them, and the
4
+ // registry's callers (the app's home) never import the review's runtime.
5
+ import { parseDestinations, type Destination } from "@quickchatai/pi-quickreview/src/reviewdest.js";
6
+
7
+ export const REVIEW_USAGE =
8
+ "usage: qcr review start|status|stop (detached runs, see `qcr review start --help`) | qcr review [branch | --uncommitted | --diff FILE|- | <PR number|URL>] [--repo OWNER/NAME] [--base REF] [--preview] [--key K] [--limit USD] [--full] [--to DEST] [--out FILE] [--debug-pi] [--json] | --help";
9
+
10
+ export type ReviewCommand =
11
+ | { help: true }
12
+ | {
13
+ help: false;
14
+ uncommitted: boolean;
15
+ /** `--diff`: a file to read the diff from, or "-" for stdin. */
16
+ diff?: string;
17
+ pr?: string;
18
+ repo?: string;
19
+ base?: string;
20
+ preview: boolean;
21
+ key?: string;
22
+ limit?: number;
23
+ full: boolean;
24
+ to?: Destination[];
25
+ out?: string;
26
+ /** `--debug-pi`: the round runs inside interactive pi. */
27
+ debugPi: boolean;
28
+ };
29
+
30
+ const usage = (why?: string): Error => new Error(why ? `${REVIEW_USAGE}\n(${why})` : REVIEW_USAGE);
31
+
32
+ /** `qcr review`'s arguments (`--json` taken off before). An unknown token is
33
+ * refused, so a mistyped flag never runs something else. */
34
+ export function parseReviewArgs(rest: readonly string[]): ReviewCommand {
35
+ let help = false;
36
+ let uncommitted = false;
37
+ let diff: string | undefined;
38
+ let preview = false;
39
+ let full = false;
40
+ let base: string | undefined;
41
+ let pr: string | undefined;
42
+ let repo: string | undefined;
43
+ let key: string | undefined;
44
+ let limit: number | undefined;
45
+ let to: Destination[] | undefined;
46
+ let out: string | undefined;
47
+ let debugPi = false;
48
+ const value = (i: number, flag: string): string => {
49
+ const v = rest[i + 1];
50
+ if (v === undefined || v.startsWith("--")) throw usage(`${flag} needs a value`);
51
+ return v;
52
+ };
53
+ for (let i = 0; i < rest.length; i++) {
54
+ const a = rest[i]!;
55
+ if (a === "--help" || a === "-h") help = true;
56
+ else if (a === "--preview") preview = true;
57
+ else if (a === "--full") full = true;
58
+ else if (a === "--debug-pi") debugPi = true;
59
+ else if (a === "--base") base = value(i++, a);
60
+ else if (a === "--diff") {
61
+ // "-" is stdin; a file whose name begins with "-" is written ./-name
62
+ const v = rest[i + 1];
63
+ if (v === undefined || (v !== "-" && v.startsWith("-")))
64
+ throw usage("--diff needs a file, or - for stdin");
65
+ diff = v;
66
+ i++;
67
+ } else if (a === "--repo") repo = value(i++, a);
68
+ else if (a === "--key") key = value(i++, a);
69
+ else if (a === "--to") {
70
+ try {
71
+ to = parseDestinations(value(i++, a));
72
+ } catch (err) {
73
+ throw usage(err instanceof Error ? err.message : String(err));
74
+ }
75
+ } else if (a === "--out") out = value(i++, a);
76
+ else if (a === "--limit") {
77
+ const n = Number(value(i++, a));
78
+ if (!Number.isFinite(n) || n <= 0)
79
+ throw usage("--limit must be a positive number of dollars");
80
+ limit = n;
81
+ } else if (a === "branch") continue;
82
+ else if (a === "--uncommitted") uncommitted = true;
83
+ else if (/^#?\d+$/.test(a) || /^https?:\/\//.test(a)) {
84
+ if (pr !== undefined) throw usage("one PR at a time");
85
+ pr = a;
86
+ } else throw usage(`unknown argument ${a}`);
87
+ }
88
+ if (help) return { help: true };
89
+ if (uncommitted && base !== undefined)
90
+ throw usage("--uncommitted reviews against HEAD; --base does not apply");
91
+ if (uncommitted && rest.includes("branch"))
92
+ throw usage("choose one target: branch or --uncommitted");
93
+ if (uncommitted && pr !== undefined) throw usage("choose one target: --uncommitted or a PR");
94
+ if (diff !== undefined) {
95
+ if (uncommitted || pr !== undefined || rest.includes("branch"))
96
+ throw usage("choose one target: branch, --uncommitted, --diff or a PR");
97
+ if (base !== undefined) throw usage("--diff carries its own changes; --base does not apply");
98
+ if (repo !== undefined) throw usage("--repo goes with a PR number or URL");
99
+ }
100
+ if (debugPi && preview) throw usage("--debug-pi runs a round; a preview starts nothing");
101
+ if (debugPi && to?.includes("pr")) throw usage("a debug round posts nothing: drop --to pr");
102
+ if (pr !== undefined && base !== undefined) throw usage("--base applies to a branch, not a PR");
103
+ if (pr === undefined && repo !== undefined) throw usage("--repo goes with a PR number or URL");
104
+ return {
105
+ help: false,
106
+ uncommitted,
107
+ ...(diff !== undefined ? { diff } : {}),
108
+ ...(pr !== undefined ? { pr } : {}),
109
+ ...(repo !== undefined ? { repo } : {}),
110
+ preview,
111
+ full,
112
+ debugPi,
113
+ ...(base !== undefined ? { base } : {}),
114
+ ...(key !== undefined ? { key } : {}),
115
+ ...(limit !== undefined ? { limit } : {}),
116
+ ...(to !== undefined ? { to } : {}),
117
+ ...(out !== undefined ? { out } : {}),
118
+ };
119
+ }
package/src/reviewcli.ts CHANGED
@@ -24,8 +24,7 @@
24
24
  // stopped short (partial); 2 usage.
25
25
  import { existsSync, openSync, readFileSync, writeSync } from "node:fs";
26
26
  import { homedir } from "node:os";
27
- import { extname, join, resolve } from "node:path";
28
- import { fileURLToPath } from "node:url";
27
+ import { join, resolve } from "node:path";
29
28
  import type { CoreConfig } from "@quickchatai/pi-quickreview/src/reviewconfig.js";
30
29
  import type { Ecosystem } from "@quickchatai/pi-quickreview/src/ecosystem.js";
31
30
  import type { ReviewMemoryPort } from "@quickchatai/pi-quickreview/src/memoryport.js";
@@ -34,7 +33,6 @@ import type { PrCredentialDeps, PrFetchRequest } from "@quickchatai/pi-quickrevi
34
33
  import {
35
34
  FALLBACK_DESTINATIONS,
36
35
  loadDefaultDestinationsIn,
37
- parseDestinations,
38
36
  planDestinations,
39
37
  saveDefaultDestinationsIn,
40
38
  type Destination,
@@ -64,9 +62,9 @@ import {
64
62
  type ReviewTarget,
65
63
  } from "@quickchatai/pi-quickreview/src/reviewapi.js";
66
64
  import { reviewHome } from "./reviewhome.js";
65
+ import { parseReviewArgs, REVIEW_USAGE, type ReviewCommand } from "./reviewargs.js";
67
66
 
68
- export const REVIEW_USAGE =
69
- "usage: qcr review start|status|stop (detached runs, see `qcr review start --help`) | qcr review [branch | --uncommitted | --diff FILE|- | <PR number|URL>] [--repo OWNER/NAME] [--base REF] [--preview] [--key K] [--limit USD] [--full] [--to DEST] [--out FILE] [--debug-pi] [--json] | --help";
67
+ export { parseReviewArgs, REVIEW_USAGE, type ReviewCommand } from "./reviewargs.js";
70
68
 
71
69
  export function reviewHelp(): string {
72
70
  return [
@@ -130,117 +128,6 @@ export function reviewHelp(): string {
130
128
  ].join("\n");
131
129
  }
132
130
 
133
- export type ReviewCommand =
134
- | { help: true }
135
- | {
136
- help: false;
137
- uncommitted: boolean;
138
- /** `--diff`: a file to read the diff from, or "-" for stdin. */
139
- diff?: string;
140
- pr?: string;
141
- repo?: string;
142
- base?: string;
143
- preview: boolean;
144
- key?: string;
145
- limit?: number;
146
- full: boolean;
147
- to?: Destination[];
148
- out?: string;
149
- /** `--debug-pi`: the round runs inside interactive pi. */
150
- debugPi: boolean;
151
- };
152
-
153
- const usage = (why?: string): Error => new Error(why ? `${REVIEW_USAGE}\n(${why})` : REVIEW_USAGE);
154
-
155
- /** `qcr review`'s arguments (`--json` taken off before). An unknown token is
156
- * refused, so a mistyped flag never runs something else. */
157
- export function parseReviewArgs(rest: readonly string[]): ReviewCommand {
158
- let help = false;
159
- let uncommitted = false;
160
- let diff: string | undefined;
161
- let preview = false;
162
- let full = false;
163
- let base: string | undefined;
164
- let pr: string | undefined;
165
- let repo: string | undefined;
166
- let key: string | undefined;
167
- let limit: number | undefined;
168
- let to: Destination[] | undefined;
169
- let out: string | undefined;
170
- let debugPi = false;
171
- const value = (i: number, flag: string): string => {
172
- const v = rest[i + 1];
173
- if (v === undefined || v.startsWith("--")) throw usage(`${flag} needs a value`);
174
- return v;
175
- };
176
- for (let i = 0; i < rest.length; i++) {
177
- const a = rest[i]!;
178
- if (a === "--help" || a === "-h") help = true;
179
- else if (a === "--preview") preview = true;
180
- else if (a === "--full") full = true;
181
- else if (a === "--debug-pi") debugPi = true;
182
- else if (a === "--base") base = value(i++, a);
183
- else if (a === "--diff") {
184
- // "-" is stdin; a file whose name begins with "-" is written ./-name
185
- const v = rest[i + 1];
186
- if (v === undefined || (v !== "-" && v.startsWith("-")))
187
- throw usage("--diff needs a file, or - for stdin");
188
- diff = v;
189
- i++;
190
- } else if (a === "--repo") repo = value(i++, a);
191
- else if (a === "--key") key = value(i++, a);
192
- else if (a === "--to") {
193
- try {
194
- to = parseDestinations(value(i++, a));
195
- } catch (err) {
196
- throw usage(err instanceof Error ? err.message : String(err));
197
- }
198
- } else if (a === "--out") out = value(i++, a);
199
- else if (a === "--limit") {
200
- const n = Number(value(i++, a));
201
- if (!Number.isFinite(n) || n <= 0)
202
- throw usage("--limit must be a positive number of dollars");
203
- limit = n;
204
- } else if (a === "branch") continue;
205
- else if (a === "--uncommitted") uncommitted = true;
206
- else if (/^#?\d+$/.test(a) || /^https?:\/\//.test(a)) {
207
- if (pr !== undefined) throw usage("one PR at a time");
208
- pr = a;
209
- } else throw usage(`unknown argument ${a}`);
210
- }
211
- if (help) return { help: true };
212
- if (uncommitted && base !== undefined)
213
- throw usage("--uncommitted reviews against HEAD; --base does not apply");
214
- if (uncommitted && rest.includes("branch"))
215
- throw usage("choose one target: branch or --uncommitted");
216
- if (uncommitted && pr !== undefined) throw usage("choose one target: --uncommitted or a PR");
217
- if (diff !== undefined) {
218
- if (uncommitted || pr !== undefined || rest.includes("branch"))
219
- throw usage("choose one target: branch, --uncommitted, --diff or a PR");
220
- if (base !== undefined) throw usage("--diff carries its own changes; --base does not apply");
221
- if (repo !== undefined) throw usage("--repo goes with a PR number or URL");
222
- }
223
- if (debugPi && preview) throw usage("--debug-pi runs a round; a preview starts nothing");
224
- if (debugPi && to?.includes("pr")) throw usage("a debug round posts nothing: drop --to pr");
225
- if (pr !== undefined && base !== undefined) throw usage("--base applies to a branch, not a PR");
226
- if (pr === undefined && repo !== undefined) throw usage("--repo goes with a PR number or URL");
227
- return {
228
- help: false,
229
- uncommitted,
230
- ...(diff !== undefined ? { diff } : {}),
231
- ...(pr !== undefined ? { pr } : {}),
232
- ...(repo !== undefined ? { repo } : {}),
233
- preview,
234
- full,
235
- debugPi,
236
- ...(base !== undefined ? { base } : {}),
237
- ...(key !== undefined ? { key } : {}),
238
- ...(limit !== undefined ? { limit } : {}),
239
- ...(to !== undefined ? { to } : {}),
240
- ...(out !== undefined ? { out } : {}),
241
- };
242
- }
243
-
244
131
  export type { ReviewRoute };
245
132
 
246
133
  /** The route a process runs on: the Action sets QCR_ROUTE=action (or runs under
@@ -465,54 +352,6 @@ export function containerRunEnv(
465
352
  return { id, name };
466
353
  }
467
354
 
468
- /** The CLI entry the detached driver re-runs: `cli` beside this module. Read
469
- * from where this module is, not from `process.argv[1]`, which is whatever the
470
- * person launched (a bin symlink, a wrapper, a test runner). */
471
- export function detachedEntry(metaUrl: string = import.meta.url): string {
472
- return fileURLToPath(new URL(`./cli${extname(fileURLToPath(metaUrl))}`, metaUrl));
473
- }
474
-
475
- /** The process `qcr review start` detaches: this node, this entry, the run's
476
- * directory and key, the review's own arguments. */
477
- export function detachedCommand(
478
- l: { dir: string; key: string; argv: readonly string[] },
479
- o: { entry?: string; execPath?: string; execArgv?: readonly string[] } = {},
480
- ): { command: string; args: string[] } {
481
- return {
482
- command: o.execPath ?? process.execPath,
483
- args: [
484
- ...(o.execArgv ?? process.execArgv),
485
- o.entry ?? detachedEntry(),
486
- "review",
487
- "--detached-run",
488
- l.dir,
489
- l.key,
490
- ...l.argv,
491
- "--json",
492
- ],
493
- };
494
- }
495
-
496
- /** The watchdog `qcr review start` detaches beside the run: this node, this
497
- * entry, the run's directory, key and driver pid. */
498
- export function watchdogCommand(
499
- w: { dir: string; key: string; pid: number },
500
- o: { entry?: string; execPath?: string; execArgv?: readonly string[] } = {},
501
- ): { command: string; args: string[] } {
502
- return {
503
- command: o.execPath ?? process.execPath,
504
- args: [
505
- ...(o.execArgv ?? process.execArgv),
506
- o.entry ?? detachedEntry(),
507
- "review",
508
- "--watchdog",
509
- w.dir,
510
- w.key,
511
- String(w.pid),
512
- ],
513
- };
514
- }
515
-
516
355
  /** The runtime `[engine] runtime` selects for `qcr review`: the SDK runtime.
517
356
  * `[pi] lens_runtime = engine` is
518
357
  * the SDK runtime with our lens tools; with `[pi] engine_wave` the runtime
@@ -578,13 +417,13 @@ export async function cmdReview(
578
417
  container !== undefined
579
418
  ? { ok: true, pid: null, container: container.name }
580
419
  : det.launchDetached({
581
- ...detachedCommand(l),
420
+ ...det.detachedCommand(l),
582
421
  dir: l.dir,
583
422
  env: process.env,
584
423
  }),
585
424
  watch: (w) => {
586
425
  det.launchDetached({
587
- ...watchdogCommand(w),
426
+ ...det.watchdogCommand(w),
588
427
  dir: w.dir,
589
428
  env: process.env,
590
429
  log: "watchdog.log",
@@ -700,7 +539,7 @@ export async function cmdReview(
700
539
  // A person's run needs a credential for every provider the review's
701
540
  // models name; a key for another provider never stands in for one.
702
541
  const { reviewLoginRefusal } = await import("./loginhint.js");
703
- const { reviewProviders } = await import("@quickchatai/pi-quickreview/src/plugin/login.js");
542
+ const { reviewProviders } = await import("@quickchatai/pi-quickreview/src/modelroute.js");
704
543
  const problem = reviewLoginRefusal({
705
544
  providers: reviewProviders(cfg),
706
545
  agentDir,
@@ -43,7 +43,8 @@ import {
43
43
  rmSync,
44
44
  writeFileSync,
45
45
  } from "node:fs";
46
- import { basename, dirname, join, resolve } from "node:path";
46
+ import { basename, dirname, extname, join, resolve } from "node:path";
47
+ import { fileURLToPath } from "node:url";
47
48
  import { stopLiveSessions } from "@quickchatai/pi-quickreview/src/engine/livesessions.js";
48
49
  import {
49
50
  HEARTBEAT_FILE,
@@ -55,9 +56,10 @@ import {
55
56
  resolveBranch,
56
57
  resolveUncommitted,
57
58
  } from "@quickchatai/pi-quickreview/src/localreview.js";
58
- import { latestLiveRound, viewRound } from "./live/read.js";
59
+ import { latestLiveRound, viewRound } from "@quickchatai/pi-quickreview/src/live/read.js";
60
+ import { lensActivityLine, lensElapsedMs } from "@quickchatai/pi-quickreview/src/live/lensline.js";
59
61
  import { parsePrTarget } from "@quickchatai/pi-quickreview/src/prtarget.js";
60
- import { parseReviewArgs } from "./reviewcli.js";
62
+ import { parseReviewArgs } from "./reviewargs.js";
61
63
  import {
62
64
  REVIEW_SCHEMA_VERSION,
63
65
  withSchemaVersion,
@@ -234,6 +236,9 @@ interface Observed {
234
236
  dir: string;
235
237
  state: RunState;
236
238
  exitCode: number | null;
239
+ /** When it ended, ms since the epoch: its exit's or its stop's time (a run
240
+ * recorded before the exit carried one has none). */
241
+ endedAt: number | null;
237
242
  result: unknown;
238
243
  }
239
244
 
@@ -243,8 +248,9 @@ function observe(home: string, runId: string, deps: Liveness): Observed | null {
243
248
  const dir = join(runsDir(home), runId);
244
249
  const run = readJson<RunRecord>(join(dir, "run.json"));
245
250
  if (run === null) return null;
246
- const exit = readJson<{ code: number }>(join(dir, "exit.json"));
247
- const stopped = existsSync(join(dir, "stopped.json"));
251
+ const exit = readJson<{ code: number; at?: number }>(join(dir, "exit.json"));
252
+ const stoppedAt = readJson<{ at?: number }>(join(dir, "stopped.json"));
253
+ const stopped = stoppedAt !== null || existsSync(join(dir, "stopped.json"));
248
254
  let state: RunState;
249
255
  // A run that was stopped and unwinds records a failing exit of its own on the
250
256
  // way out (an aborted turn has no result); `stop` is what ended it, so that
@@ -268,7 +274,15 @@ function observe(home: string, runId: string, deps: Liveness): Observed | null {
268
274
  } catch {
269
275
  // no output yet
270
276
  }
271
- return { run, dir, state, exitCode: exit?.code ?? null, result };
277
+ const at = (v: unknown): number | null =>
278
+ typeof v === "number" && Number.isFinite(v) ? v : null;
279
+ const endedAt =
280
+ state === "running"
281
+ ? null
282
+ : state === "stopped"
283
+ ? (at(stoppedAt?.at) ?? at(exit?.at))
284
+ : at(exit?.at);
285
+ return { run, dir, state, exitCode: exit?.code ?? null, endedAt, result };
272
286
  }
273
287
 
274
288
  function allRuns(home: string, deps: Liveness): Observed[] {
@@ -285,7 +299,11 @@ function allRuns(home: string, deps: Liveness): Observed[] {
285
299
  }
286
300
 
287
301
  /** What a running review is doing, from its LiveEvent log projected to
288
- * round-progress v2 (src/live/): the same snapshot the bot's supervisor reads. */
302
+ * round-progress v2 (packages/pi-plugin/src/live/projection.ts): the same
303
+ * snapshot the bot's supervisor reads. Each lens carries what it is doing as
304
+ * it happens: its tool count, its current tool and the path that tool is on,
305
+ * and its time so far (a running lens's counts on from its start between
306
+ * updates). `line` is the same entry as one line a person reads. */
289
307
  interface LiveProgress {
290
308
  round: number;
291
309
  parent_status: "active" | "settled";
@@ -298,6 +316,8 @@ interface LiveProgress {
298
316
  duration_ms: number;
299
317
  tool_count: number;
300
318
  current_tool?: string;
319
+ current_target?: string;
320
+ line: string;
301
321
  }>;
302
322
  }
303
323
 
@@ -308,7 +328,7 @@ interface Progress {
308
328
 
309
329
  /** How far a run got: the lenses its stage record names (written when the round
310
330
  * ends) and, while it runs, its live log. */
311
- function progress(dir: string): Progress | null {
331
+ function progress(dir: string, now: number): Progress | null {
312
332
  const work = join(dir, "work");
313
333
  let dirs: string[] = [];
314
334
  try {
@@ -342,9 +362,11 @@ function progress(dir: string): Progress | null {
342
362
  agents: snapshot.agents.map((a) => ({
343
363
  agent: a.agent,
344
364
  status: a.status,
345
- duration_ms: a.duration_ms,
365
+ duration_ms: lensElapsedMs(a, now) ?? a.duration_ms,
346
366
  tool_count: a.tool_count,
347
367
  ...(a.current_tool ? { current_tool: a.current_tool } : {}),
368
+ ...(a.current_target ? { current_target: a.current_target } : {}),
369
+ line: lensActivityLine(a, { nowMs: now }),
348
370
  })),
349
371
  };
350
372
  }
@@ -358,9 +380,11 @@ function view(o: Observed, now: number): Record<string, unknown> {
358
380
  run_id: o.run.run_id,
359
381
  state: o.state,
360
382
  target: o.run.label,
361
- elapsed_s: Math.max(0, Math.round((now - o.run.started_at) / 1000)),
383
+ started_at: o.run.started_at,
384
+ ...(o.endedAt !== null ? { ended_at: o.endedAt } : {}),
385
+ elapsed_s: Math.max(0, Math.round(((o.endedAt ?? now) - o.run.started_at) / 1000)),
362
386
  exit_code: o.exitCode,
363
- ...(o.state === "running" ? { progress: progress(o.dir) } : { result: o.result }),
387
+ ...(o.state === "running" ? { progress: progress(o.dir, now) } : { result: o.result }),
364
388
  ...(o.state === "failed" && o.exitCode === null
365
389
  ? { errors: [`the review process ended without a result; see ${join(o.dir, "driver.log")}`] }
366
390
  : {}),
@@ -378,6 +402,7 @@ function text(v: Record<string, unknown>): string {
378
402
  const names = by(s);
379
403
  if (names.length > 0) bits.push(`${s}: ${names.join(", ")}`);
380
404
  }
405
+ for (const a of p.live.agents) if (a.status === "running") bits.push(` ${a.line}`);
381
406
  bits.push(`now: ${p.live.last_activity}`);
382
407
  }
383
408
  const errs = v["errors"] as string[] | undefined;
@@ -385,6 +410,24 @@ function text(v: Record<string, unknown>): string {
385
410
  return bits.join("\n");
386
411
  }
387
412
 
413
+ /** One run as `status --json` reports it, with the review's own arguments. */
414
+ export interface RunView {
415
+ view: Record<string, unknown>;
416
+ argv: readonly string[];
417
+ }
418
+
419
+ /** Every run in the registry at `home`, oldest first, each as `status`
420
+ * reports it: what a host re-attaches to after a restart. */
421
+ export function listRunViews(home: string, deps: Liveness): RunView[] {
422
+ return allRuns(home, deps).map((o) => ({ view: view(o, deps.now()), argv: o.run.argv }));
423
+ }
424
+
425
+ /** One run as `status` reports it; null when there is no such run. */
426
+ export function runView(home: string, runId: string, deps: Liveness): RunView | null {
427
+ const o = observe(home, runId, deps);
428
+ return o === null ? null : { view: view(o, deps.now()), argv: o.run.argv };
429
+ }
430
+
388
431
  /** Run one `start | status | stop`; resolves its exit code. */
389
432
  export async function runDetachedVerb(
390
433
  verb: "start" | "status" | "stop",
@@ -652,7 +695,7 @@ export async function runDetachedChild(o: {
652
695
  proc.off("SIGINT", onSignal);
653
696
  proc.off("SIGTERM", onSignal);
654
697
  }
655
- writeFileSync(join(o.dir, "exit.json"), JSON.stringify({ code }));
698
+ writeFileSync(join(o.dir, "exit.json"), JSON.stringify({ code, at: Date.now() }));
656
699
  // only this run's own claim: a stop frees the lock for a newer run
657
700
  const lock = lockPath(dirname(dirname(o.dir)), o.key);
658
701
  if (readJson<{ run_id?: string }>(lock)?.run_id === basename(o.dir))
@@ -725,10 +768,61 @@ export async function runWatchdog(
725
768
  }),
726
769
  )}\n`,
727
770
  );
728
- writeFileSync(join(w.dir, "exit.json"), JSON.stringify({ code: WATCHDOG_EXIT_CODE }));
771
+ writeFileSync(
772
+ join(w.dir, "exit.json"),
773
+ JSON.stringify({ code: WATCHDOG_EXIT_CODE, at: deps.now() }),
774
+ );
729
775
  rmSync(lockPath(dirname(dirname(w.dir)), w.key), { force: true });
730
776
  return "killed";
731
777
  }
732
778
  await deps.sleep(deps.pollMs ?? 2_000);
733
779
  }
734
780
  }
781
+
782
+ /** The CLI entry the detached driver re-runs: `cli` beside this module. Read
783
+ * from where this module is, not from `process.argv[1]`, which is whatever the
784
+ * person launched (a bin symlink, a wrapper, a test runner). */
785
+ export function detachedEntry(metaUrl: string = import.meta.url): string {
786
+ return fileURLToPath(new URL(`./cli${extname(fileURLToPath(metaUrl))}`, metaUrl));
787
+ }
788
+
789
+ /** The process `qcr review start` detaches: this node, this entry, the run's
790
+ * directory and key, the review's own arguments. */
791
+ export function detachedCommand(
792
+ l: { dir: string; key: string; argv: readonly string[] },
793
+ o: { entry?: string; execPath?: string; execArgv?: readonly string[] } = {},
794
+ ): { command: string; args: string[] } {
795
+ return {
796
+ command: o.execPath ?? process.execPath,
797
+ args: [
798
+ ...(o.execArgv ?? process.execArgv),
799
+ o.entry ?? detachedEntry(),
800
+ "review",
801
+ "--detached-run",
802
+ l.dir,
803
+ l.key,
804
+ ...l.argv,
805
+ "--json",
806
+ ],
807
+ };
808
+ }
809
+
810
+ /** The watchdog `qcr review start` detaches beside the run: this node, this
811
+ * entry, the run's directory, key and driver pid. */
812
+ export function watchdogCommand(
813
+ w: { dir: string; key: string; pid: number },
814
+ o: { entry?: string; execPath?: string; execArgv?: readonly string[] } = {},
815
+ ): { command: string; args: string[] } {
816
+ return {
817
+ command: o.execPath ?? process.execPath,
818
+ args: [
819
+ ...(o.execArgv ?? process.execArgv),
820
+ o.entry ?? detachedEntry(),
821
+ "review",
822
+ "--watchdog",
823
+ w.dir,
824
+ w.key,
825
+ String(w.pid),
826
+ ],
827
+ };
828
+ }