@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.
- package/README.md +137 -57
- package/bin/qcr.js +1 -1
- package/config.example.toml +60 -14
- package/deploy/ansible/README.md +20 -12
- package/deploy/ansible/inventory.example.ini +1 -1
- package/deploy/collect-secrets.sh +1 -1
- package/deploy/qcr-update.sh +1 -1
- package/dist/extensions/cli/home.js +14 -0
- package/dist/extensions/cli/home.js.map +1 -0
- package/dist/extensions/cli/login-route.js +5 -0
- package/dist/extensions/cli/login-route.js.map +1 -0
- package/dist/extensions/cli/selfimprove.js +18 -0
- package/dist/extensions/cli/selfimprove.js.map +1 -0
- package/dist/extensions/home.js +350 -0
- package/dist/extensions/home.js.map +1 -0
- package/dist/extensions/login-route.js +180 -0
- package/dist/extensions/login-route.js.map +1 -0
- package/dist/extensions/onboarding.js +35 -11
- package/dist/extensions/onboarding.js.map +1 -1
- package/dist/extensions/selfimprove.js +49 -0
- package/dist/extensions/selfimprove.js.map +1 -0
- package/dist/extensions/setup-scan.js +23 -592
- package/dist/extensions/setup-scan.js.map +1 -1
- package/dist/extensions/setup.js +2 -47
- package/dist/extensions/setup.js.map +1 -1
- package/dist/src/archive/restore.js +18 -15
- package/dist/src/archive/restore.js.map +1 -1
- package/dist/src/botworker.js +13 -2
- package/dist/src/botworker.js.map +1 -1
- package/dist/src/channelfs.js +3 -26
- package/dist/src/channelfs.js.map +1 -1
- package/dist/src/ciretry.js +2 -1
- package/dist/src/ciretry.js.map +1 -1
- package/dist/src/cli.js +54 -61
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliops.js +3 -2
- package/dist/src/cliops.js.map +1 -1
- package/dist/src/commands.js +25 -2
- package/dist/src/commands.js.map +1 -1
- package/dist/src/config.js +32 -2
- package/dist/src/config.js.map +1 -1
- package/dist/src/daemon.js +50 -8
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/debuground.js +1 -0
- package/dist/src/debuground.js.map +1 -1
- package/dist/src/debugroundpi.js +6 -1
- package/dist/src/debugroundpi.js.map +1 -1
- package/dist/src/dispatch.js +5 -4
- package/dist/src/dispatch.js.map +1 -1
- package/dist/src/engine/caserunner-sdk.js +15 -251
- package/dist/src/engine/caserunner-sdk.js.map +1 -1
- package/dist/src/engine/debugpi.js +17 -5
- package/dist/src/engine/debugpi.js.map +1 -1
- package/dist/src/execboundary.js +10 -3
- package/dist/src/execboundary.js.map +1 -1
- package/dist/src/github.js +51 -16
- package/dist/src/github.js.map +1 -1
- package/dist/src/githubtoken.js +346 -0
- package/dist/src/githubtoken.js.map +1 -0
- package/dist/src/home/adapters.js +349 -0
- package/dist/src/home/adapters.js.map +1 -0
- package/dist/src/home/advanced.js +318 -0
- package/dist/src/home/advanced.js.map +1 -0
- package/dist/src/home/advancedtools.js +57 -0
- package/dist/src/home/advancedtools.js.map +1 -0
- package/dist/src/home/autoreview.js +325 -0
- package/dist/src/home/autoreview.js.map +1 -0
- package/dist/src/home/daemonstatus.js +32 -0
- package/dist/src/home/daemonstatus.js.map +1 -0
- package/dist/src/home/devicelogin.js +61 -0
- package/dist/src/home/devicelogin.js.map +1 -0
- package/dist/src/home/launch.js +202 -0
- package/dist/src/home/launch.js.map +1 -0
- package/dist/src/home/loginowner.js +205 -0
- package/dist/src/home/loginowner.js.map +1 -0
- package/dist/src/home/memory.js +122 -0
- package/dist/src/home/memory.js.map +1 -0
- package/dist/src/home/model.js +811 -0
- package/dist/src/home/model.js.map +1 -0
- package/dist/src/home/panel.js +520 -0
- package/dist/src/home/panel.js.map +1 -0
- package/dist/src/home/ports.js +19 -0
- package/dist/src/home/ports.js.map +1 -0
- package/dist/src/home/review.js +163 -0
- package/dist/src/home/review.js.map +1 -0
- package/dist/src/home/reviews.js +478 -0
- package/dist/src/home/reviews.js.map +1 -0
- package/dist/src/home/setup.js +507 -0
- package/dist/src/home/setup.js.map +1 -0
- package/dist/src/home/setupadapters.js +214 -0
- package/dist/src/home/setupadapters.js.map +1 -0
- package/dist/src/home/setupcard.js +199 -0
- package/dist/src/home/setupcard.js.map +1 -0
- package/dist/src/home/setuptools.js +45 -0
- package/dist/src/home/setuptools.js.map +1 -0
- package/dist/src/home/spend.js +160 -0
- package/dist/src/home/spend.js.map +1 -0
- package/dist/src/home/switches.js +209 -0
- package/dist/src/home/switches.js.map +1 -0
- package/dist/src/home/switchon.js +439 -0
- package/dist/src/home/switchon.js.map +1 -0
- package/dist/src/home/toolnames.js +41 -0
- package/dist/src/home/toolnames.js.map +1 -0
- package/dist/src/home/tune.js +341 -0
- package/dist/src/home/tune.js.map +1 -0
- package/dist/src/home/words.js +7 -0
- package/dist/src/home/words.js.map +1 -0
- package/dist/src/homes.js +14 -0
- package/dist/src/homes.js.map +1 -1
- package/dist/src/install.js +1 -1
- package/dist/src/install.js.map +1 -1
- package/dist/src/ipc/workerspec.js +8 -0
- package/dist/src/ipc/workerspec.js.map +1 -1
- package/dist/src/live/publish.js +1 -1
- package/dist/src/live/publish.js.map +1 -1
- package/dist/src/livecli.js +1 -1
- package/dist/src/livecli.js.map +1 -1
- package/dist/src/loginhint.js +8 -3
- package/dist/src/loginhint.js.map +1 -1
- package/dist/src/loginrefresh/handover.js +121 -0
- package/dist/src/loginrefresh/handover.js.map +1 -0
- package/dist/src/loginrefresh/lock.js +54 -3
- package/dist/src/loginrefresh/lock.js.map +1 -1
- package/dist/src/loginrefresh/pirefresh.js +3 -0
- package/dist/src/loginrefresh/pirefresh.js.map +1 -1
- package/dist/src/loginrefresh/refresher.js +33 -5
- package/dist/src/loginrefresh/refresher.js.map +1 -1
- package/dist/src/loginrefresh/supervisor.js +18 -1
- package/dist/src/loginrefresh/supervisor.js.map +1 -1
- package/dist/src/npmverbs.js +46 -14
- package/dist/src/npmverbs.js.map +1 -1
- package/dist/src/onboard/choices.js +35 -4
- package/dist/src/onboard/choices.js.map +1 -1
- package/dist/src/onboard/engine.js +32 -306
- package/dist/src/onboard/engine.js.map +1 -1
- package/dist/src/onboard/pipin.js +22 -0
- package/dist/src/onboard/pipin.js.map +1 -0
- package/dist/src/onboard/stepforward.js +41 -0
- package/dist/src/onboard/stepforward.js.map +1 -0
- package/dist/src/onboard/steps/appslug.js +4 -1
- package/dist/src/onboard/steps/appslug.js.map +1 -1
- package/dist/src/onboard/steps/bringup.js +3 -2
- package/dist/src/onboard/steps/bringup.js.map +1 -1
- package/dist/src/onboard/steps/compose.js +29 -0
- package/dist/src/onboard/steps/compose.js.map +1 -1
- package/dist/src/onboard/steps/configfill.js +120 -39
- package/dist/src/onboard/steps/configfill.js.map +1 -1
- package/dist/src/onboard/steps/githubaccount.js +595 -0
- package/dist/src/onboard/steps/githubaccount.js.map +1 -0
- package/dist/src/onboard/steps/githubapp.js +65 -7
- package/dist/src/onboard/steps/githubapp.js.map +1 -1
- package/dist/src/onboard/steps/golive.js +2 -0
- package/dist/src/onboard/steps/golive.js.map +1 -1
- package/dist/src/onboard/steps/index.js +11 -0
- package/dist/src/onboard/steps/index.js.map +1 -1
- package/dist/src/onboard/steps/ingress.js +80 -25
- package/dist/src/onboard/steps/ingress.js.map +1 -1
- package/dist/src/onboard/steps/llmauth.js +103 -30
- package/dist/src/onboard/steps/llmauth.js.map +1 -1
- package/dist/src/onboard/steps/loginshare.js +213 -0
- package/dist/src/onboard/steps/loginshare.js.map +1 -0
- package/dist/src/onboard/steps/memory.js +67 -3
- package/dist/src/onboard/steps/memory.js.map +1 -1
- package/dist/src/onboard/steps/proof.js +366 -56
- package/dist/src/onboard/steps/proof.js.map +1 -1
- package/dist/src/onboard/steps/repoconfig.js +72 -0
- package/dist/src/onboard/steps/repoconfig.js.map +1 -0
- package/dist/src/onboard/steps/topology.js +6 -6
- package/dist/src/onboard/steps/topology.js.map +1 -1
- package/dist/src/onboard/steps/transport.js +9 -1
- package/dist/src/onboard/steps/transport.js.map +1 -1
- package/dist/src/onboard/steps/tuiroute.js +102 -0
- package/dist/src/onboard/steps/tuiroute.js.map +1 -0
- package/dist/src/onboard/steps/verifyup.js +17 -1
- package/dist/src/onboard/steps/verifyup.js.map +1 -1
- package/dist/src/onboard/toolnames.js +30 -0
- package/dist/src/onboard/toolnames.js.map +1 -0
- package/dist/src/operatorwrite.js +23 -0
- package/dist/src/operatorwrite.js.map +1 -0
- package/dist/src/outcomes.js +4 -1
- package/dist/src/outcomes.js.map +1 -1
- package/dist/src/piagentdir.js +13 -0
- package/dist/src/piagentdir.js.map +1 -0
- package/dist/src/pilogin.js +31 -16
- package/dist/src/pilogin.js.map +1 -1
- package/dist/src/posting-journal.js +2 -1
- package/dist/src/posting-journal.js.map +1 -1
- package/dist/src/posting-outbox.js +6 -2
- package/dist/src/posting-outbox.js.map +1 -1
- package/dist/src/posting-receipt.js +14 -1
- package/dist/src/posting-receipt.js.map +1 -1
- package/dist/src/qcrbin.js +16 -11
- package/dist/src/qcrbin.js.map +1 -1
- package/dist/src/recheck.js +2 -1
- package/dist/src/recheck.js.map +1 -1
- package/dist/src/reconcile.js +22 -2
- package/dist/src/reconcile.js.map +1 -1
- package/dist/src/reviewargs.js +122 -0
- package/dist/src/reviewargs.js.map +1 -0
- package/dist/src/reviewcli.js +7 -161
- package/dist/src/reviewcli.js.map +1 -1
- package/dist/src/reviewdetach.js +76 -11
- package/dist/src/reviewdetach.js.map +1 -1
- package/dist/src/reviewhistory.js +32 -6
- package/dist/src/reviewhistory.js.map +1 -1
- package/dist/src/reviewnow.js +45 -0
- package/dist/src/reviewnow.js.map +1 -0
- package/dist/src/scancli.js +39 -691
- package/dist/src/scancli.js.map +1 -1
- package/dist/src/selfimprove.js +66 -0
- package/dist/src/selfimprove.js.map +1 -0
- package/dist/src/setup/adapters.js +5 -223
- package/dist/src/setup/adapters.js.map +1 -1
- package/dist/src/setup/caserunner.js +15 -249
- package/dist/src/setup/caserunner.js.map +1 -1
- package/dist/src/setup/engine.js +32 -341
- package/dist/src/setup/engine.js.map +1 -1
- package/dist/src/setup/facts.js +3 -592
- package/dist/src/setup/facts.js.map +1 -1
- package/dist/src/setup/findings.js +3 -947
- package/dist/src/setup/findings.js.map +1 -1
- package/dist/src/setup/presentation.js +3 -8
- package/dist/src/setup/presentation.js.map +1 -1
- package/dist/src/setup/scan.js +3 -724
- package/dist/src/setup/scan.js.map +1 -1
- package/dist/src/setup/scanbuild.js +9 -92
- package/dist/src/setup/scanbuild.js.map +1 -1
- package/dist/src/setup/scanlease.js +3 -250
- package/dist/src/setup/scanlease.js.map +1 -1
- package/dist/src/setup/scanreport.js +3 -288
- package/dist/src/setup/scanreport.js.map +1 -1
- package/dist/src/setup/scanrun.js +3 -1732
- package/dist/src/setup/scanrun.js.map +1 -1
- package/dist/src/setup/scantree.js +3 -256
- package/dist/src/setup/scantree.js.map +1 -1
- package/dist/src/setup/stage2request.js +3 -258
- package/dist/src/setup/stage2request.js.map +1 -1
- package/dist/src/setup/unitcut.js +3 -436
- package/dist/src/setup/unitcut.js.map +1 -1
- package/dist/src/state.js +2 -0
- package/dist/src/state.js.map +1 -1
- package/dist/src/statemachine.js +4 -1
- package/dist/src/statemachine.js.map +1 -1
- package/dist/src/takeover.js +1 -0
- package/dist/src/takeover.js.map +1 -1
- package/dist/src/takeoverpi.js +15 -1
- package/dist/src/takeoverpi.js.map +1 -1
- package/dist/src/tui/app.js +49 -0
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/board.js +88 -12
- package/dist/src/tui/board.js.map +1 -1
- package/dist/src/tui/cmdbar.js +4 -1
- package/dist/src/tui/cmdbar.js.map +1 -1
- package/dist/src/tui/companion.js +76 -11
- package/dist/src/tui/companion.js.map +1 -1
- package/dist/src/tui/connect.js +7 -1
- package/dist/src/tui/connect.js.map +1 -1
- package/dist/src/tui/deploytab.js +2 -2
- package/dist/src/tui/deploytab.js.map +1 -1
- package/dist/src/tui/filters.js +5 -2
- package/dist/src/tui/filters.js.map +1 -1
- package/dist/src/tui/ghread.js +61 -5
- package/dist/src/tui/ghread.js.map +1 -1
- package/dist/src/tui/index.js +109 -20
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/memforward.js +39 -2
- package/dist/src/tui/memforward.js.map +1 -1
- package/dist/src/tui/memory.js +3 -16
- package/dist/src/tui/memory.js.map +1 -1
- package/dist/src/tui/memoryurl.js +22 -0
- package/dist/src/tui/memoryurl.js.map +1 -0
- package/dist/src/tui/model.js +25 -0
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/prpause.js +7 -0
- package/dist/src/tui/prpause.js.map +1 -1
- package/dist/src/tui/remote.js +7 -1
- package/dist/src/tui/remote.js.map +1 -1
- package/dist/src/tui/route.js +143 -0
- package/dist/src/tui/route.js.map +1 -0
- package/dist/src/tui/setupstate.js +32 -0
- package/dist/src/tui/setupstate.js.map +1 -0
- package/dist/src/tui/sshtriage.js +102 -5
- package/dist/src/tui/sshtriage.js.map +1 -1
- package/dist/src/tui/theme.js +2 -1
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/unwedge.js +2 -12
- package/dist/src/tui/unwedge.js.map +1 -1
- package/dist/src/verdicts.js +17 -5
- package/dist/src/verdicts.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/src/webhook.js +12 -1
- package/dist/src/webhook.js.map +1 -1
- package/dist/src/wire.js +34 -8
- package/dist/src/wire.js.map +1 -1
- package/dist/src/workerworld.js +11 -1
- package/dist/src/workerworld.js.map +1 -1
- package/docker/README.md +1 -1
- package/docker/companion/entrypoint.sh +20 -1
- package/docker/entrypoint.sh +57 -6
- package/docker-compose.yml +16 -1
- package/extensions/cli/home.ts +14 -0
- package/extensions/cli/login-route.ts +10 -0
- package/extensions/cli/selfimprove.ts +18 -0
- package/extensions/home.ts +484 -0
- package/extensions/login-route.ts +220 -0
- package/extensions/onboarding.ts +41 -11
- package/extensions/selfimprove.ts +65 -0
- package/extensions/setup-scan.ts +79 -766
- package/extensions/setup.ts +14 -70
- package/package.json +6 -4
- package/prompts/onboarding.md +58 -18
- package/schemas/review-result.v1.json +60 -0
- package/scripts/deploy.sh +8 -6
- package/scripts/mint-token.sh +25 -4
- package/scripts/qcr-host.sh +28 -5
- package/scripts/vm-ghcr-login.sh +7 -4
- package/src/archive/restore.ts +18 -21
- package/src/botworker.ts +13 -2
- package/src/channelfs.ts +3 -26
- package/src/ciretry.ts +2 -1
- package/src/cli.ts +59 -67
- package/src/cliops.ts +3 -2
- package/src/commands.ts +28 -2
- package/src/config.ts +62 -4
- package/src/daemon.ts +58 -7
- package/src/debuground.ts +3 -0
- package/src/debugroundpi.ts +12 -1
- package/src/dispatch.ts +5 -4
- package/src/engine/caserunner-sdk.ts +29 -314
- package/src/engine/debugpi.ts +17 -5
- package/src/execboundary.ts +16 -4
- package/src/github.ts +57 -15
- package/src/githubtoken.ts +435 -0
- package/src/home/adapters.ts +418 -0
- package/src/home/advanced.ts +421 -0
- package/src/home/advancedtools.ts +96 -0
- package/src/home/autoreview.ts +385 -0
- package/src/home/daemonstatus.ts +55 -0
- package/src/home/devicelogin.ts +107 -0
- package/src/home/launch.ts +257 -0
- package/src/home/loginowner.ts +248 -0
- package/src/home/memory.ts +176 -0
- package/src/home/model.ts +965 -0
- package/src/home/panel.ts +666 -0
- package/src/home/ports.ts +338 -0
- package/src/home/review.ts +206 -0
- package/src/home/reviews.ts +565 -0
- package/src/home/setup.ts +689 -0
- package/src/home/setupadapters.ts +217 -0
- package/src/home/setupcard.ts +288 -0
- package/src/home/setuptools.ts +57 -0
- package/src/home/spend.ts +194 -0
- package/src/home/switches.ts +293 -0
- package/src/home/switchon.ts +554 -0
- package/src/home/toolnames.ts +46 -0
- package/src/home/tune.ts +405 -0
- package/src/home/words.ts +8 -0
- package/src/homes.ts +17 -0
- package/src/install.ts +1 -1
- package/src/ipc/workerspec.ts +15 -0
- package/src/live/publish.ts +4 -1
- package/src/livecli.ts +6 -1
- package/src/loginhint.ts +8 -3
- package/src/loginrefresh/handover.ts +138 -0
- package/src/loginrefresh/lock.ts +72 -3
- package/src/loginrefresh/pirefresh.ts +3 -0
- package/src/loginrefresh/refresher.ts +34 -5
- package/src/loginrefresh/supervisor.ts +25 -2
- package/src/npmverbs.ts +55 -13
- package/src/onboard/choices.ts +40 -4
- package/src/onboard/engine.ts +70 -421
- package/src/onboard/pipin.ts +31 -0
- package/src/onboard/stepforward.ts +61 -0
- package/src/onboard/steps/appslug.ts +4 -1
- package/src/onboard/steps/bringup.ts +3 -2
- package/src/onboard/steps/compose.ts +31 -0
- package/src/onboard/steps/configfill.ts +144 -40
- package/src/onboard/steps/githubaccount.ts +716 -0
- package/src/onboard/steps/githubapp.ts +92 -8
- package/src/onboard/steps/golive.ts +2 -0
- package/src/onboard/steps/index.ts +11 -0
- package/src/onboard/steps/ingress.ts +98 -31
- package/src/onboard/steps/llmauth.ts +114 -29
- package/src/onboard/steps/loginshare.ts +258 -0
- package/src/onboard/steps/memory.ts +76 -3
- package/src/onboard/steps/proof.ts +473 -55
- package/src/onboard/steps/repoconfig.ts +85 -0
- package/src/onboard/steps/topology.ts +7 -6
- package/src/onboard/steps/transport.ts +15 -0
- package/src/onboard/steps/tuiroute.ts +119 -0
- package/src/onboard/steps/verifyup.ts +20 -1
- package/src/onboard/toolnames.ts +34 -0
- package/src/operatorwrite.ts +25 -0
- package/src/outcomes.ts +8 -1
- package/src/piagentdir.ts +12 -0
- package/src/pilogin.ts +32 -16
- package/src/posting-journal.ts +6 -1
- package/src/posting-outbox.ts +10 -2
- package/src/posting-receipt.ts +17 -0
- package/src/qcrbin.ts +16 -11
- package/src/recheck.ts +2 -1
- package/src/reconcile.ts +29 -3
- package/src/reviewargs.ts +119 -0
- package/src/reviewcli.ts +6 -167
- package/src/reviewdetach.ts +107 -13
- package/src/reviewhistory.ts +20 -6
- package/src/reviewnow.ts +76 -0
- package/src/scancli.ts +75 -826
- package/src/selfimprove.ts +90 -0
- package/src/setup/adapters.ts +13 -221
- package/src/setup/caserunner.ts +34 -273
- package/src/setup/engine.ts +62 -496
- package/src/setup/facts.ts +3 -627
- package/src/setup/findings.ts +3 -1264
- package/src/setup/presentation.ts +3 -18
- package/src/setup/scan.ts +3 -1033
- package/src/setup/scanbuild.ts +20 -114
- package/src/setup/scanlease.ts +3 -297
- package/src/setup/scanreport.ts +3 -373
- package/src/setup/scanrun.ts +3 -2268
- package/src/setup/scantree.ts +3 -259
- package/src/setup/stage2request.ts +3 -347
- package/src/setup/unitcut.ts +3 -523
- package/src/state.ts +43 -0
- package/src/statemachine.ts +4 -1
- package/src/takeover.ts +5 -0
- package/src/takeoverpi.ts +16 -2
- package/src/tui/app.ts +47 -0
- package/src/tui/board.ts +111 -14
- package/src/tui/cmdbar.ts +4 -1
- package/src/tui/companion.ts +80 -11
- package/src/tui/connect.ts +7 -1
- package/src/tui/deploytab.ts +2 -2
- package/src/tui/filters.ts +7 -3
- package/src/tui/ghread.ts +74 -7
- package/src/tui/index.ts +117 -23
- package/src/tui/memforward.ts +52 -3
- package/src/tui/memory.ts +3 -15
- package/src/tui/memoryurl.ts +22 -0
- package/src/tui/model.ts +36 -2
- package/src/tui/prpause.ts +11 -2
- package/src/tui/remote.ts +9 -1
- package/src/tui/route.ts +173 -0
- package/src/tui/setupstate.ts +56 -0
- package/src/tui/sshtriage.ts +127 -6
- package/src/tui/theme.ts +2 -1
- package/src/tui/unwedge.ts +2 -14
- package/src/verdicts.ts +23 -5
- package/src/version.ts +1 -1
- package/src/webhook.ts +10 -1
- package/src/wire.ts +42 -9
- package/src/workerworld.ts +16 -2
- package/dist/src/live/projection.js +0 -450
- package/dist/src/live/projection.js.map +0 -1
- package/dist/src/live/read.js +0 -60
- package/dist/src/live/read.js.map +0 -1
- package/dist/src/onboard/launch.js +0 -83
- package/dist/src/onboard/launch.js.map +0 -1
- package/dist/src/setup/ledger.js +0 -207
- package/dist/src/setup/ledger.js.map +0 -1
- package/dist/src/setup/stage2.js +0 -617
- package/dist/src/setup/stage2.js.map +0 -1
- package/dist/src/setup/textsniff.js +0 -89
- package/dist/src/setup/textsniff.js.map +0 -1
- package/src/live/projection.ts +0 -491
- package/src/live/read.ts +0 -86
- package/src/onboard/launch.ts +0 -115
- package/src/setup/ledger.ts +0 -324
- package/src/setup/stage2.ts +0 -926
- package/src/setup/textsniff.ts +0 -95
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
import { KEEP_IT_HERE, postAnswer } from "@quickchatai/pi-quickreview/src/plugin/messages.js";
|
|
2
|
+
import { PENDING, known, unknown, } from "./ports.js";
|
|
3
|
+
import { attachedRun, foldStatus, plannedRun, quoteRun, reviewMessage, runReview, } from "./review.js";
|
|
4
|
+
import { switchAutoReview, switchMemory } from "./switches.js";
|
|
5
|
+
import { money, plural } from "./words.js";
|
|
6
|
+
/** After a re-login, auto-review's own "login expired" is taken as old news
|
|
7
|
+
* for this long: its status lags the refresher's by a poll or two. */
|
|
8
|
+
export const RELOGIN_GRACE_MS = 5 * 60_000;
|
|
9
|
+
/** How long the cursor rests on a PR before the home prices it. */
|
|
10
|
+
export const PRICE_REST_MS = 500;
|
|
11
|
+
/** How often a running review's status is read. */
|
|
12
|
+
export const STATUS_POLL_MS = 2000;
|
|
13
|
+
/** "about $1.33, stops at $2.66", or what is not known of it. */
|
|
14
|
+
export function priceWords(est, cap) {
|
|
15
|
+
if (est === null)
|
|
16
|
+
return "price unknown";
|
|
17
|
+
return `about ${money(est)}${cap === null ? ", no hard stop" : `, stops at ${money(cap)}`}`;
|
|
18
|
+
}
|
|
19
|
+
const noTimer = () => () => { };
|
|
20
|
+
/** The provider of a `provider/id` model. */
|
|
21
|
+
export function providerOf(model) {
|
|
22
|
+
return model.split("/")[0] ?? model;
|
|
23
|
+
}
|
|
24
|
+
/** The provider as a person names it. */
|
|
25
|
+
export function providerLabel(provider) {
|
|
26
|
+
return provider === "openai-codex" || provider === "openai" ? "OpenAI" : provider;
|
|
27
|
+
}
|
|
28
|
+
export function pullUrl(repo, pr) {
|
|
29
|
+
return `https://github.com/${repo}/pull/${pr}`;
|
|
30
|
+
}
|
|
31
|
+
/** A short age: 5m, 3h, 2d. */
|
|
32
|
+
export function ago(thenMs, nowMs) {
|
|
33
|
+
if (!Number.isFinite(thenMs))
|
|
34
|
+
return "?";
|
|
35
|
+
const m = Math.max(0, (nowMs - thenMs) / 60_000);
|
|
36
|
+
return m < 60
|
|
37
|
+
? `${Math.round(m)}m`
|
|
38
|
+
: m < 1440
|
|
39
|
+
? `${Math.round(m / 60)}h`
|
|
40
|
+
: `${Math.round(m / 1440)}d`;
|
|
41
|
+
}
|
|
42
|
+
const message = (err) => (err instanceof Error ? err.message : String(err));
|
|
43
|
+
/** A port's answer; a port that throws instead of answering is unknown. */
|
|
44
|
+
async function safe(read) {
|
|
45
|
+
try {
|
|
46
|
+
return await read();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return unknown(message(err));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** An action port's outcome; one that throws is a refusal with its message. */
|
|
53
|
+
async function attempt(act) {
|
|
54
|
+
try {
|
|
55
|
+
return await act();
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
return { ok: false, why: message(err) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const later = (a, b) => a === undefined ? b : b === undefined ? a : b.at > a.at ? b : a;
|
|
62
|
+
const newestFirst = (a, b) => b.number - a.number;
|
|
63
|
+
const valueOf = (s) => (s.kind === "known" ? s.value : undefined);
|
|
64
|
+
const nullable = (s) => (s.kind === "known" ? s.value : null);
|
|
65
|
+
export class HomeModel {
|
|
66
|
+
o;
|
|
67
|
+
state = {
|
|
68
|
+
login: PENDING,
|
|
69
|
+
github: PENDING,
|
|
70
|
+
repo: PENDING,
|
|
71
|
+
pulls: PENDING,
|
|
72
|
+
reviews: PENDING,
|
|
73
|
+
autoRounds: PENDING,
|
|
74
|
+
autoReview: PENDING,
|
|
75
|
+
trigger: null,
|
|
76
|
+
memory: PENDING,
|
|
77
|
+
spend: PENDING,
|
|
78
|
+
loginHealth: null,
|
|
79
|
+
};
|
|
80
|
+
/** Slots a switch shows its own state in while it runs (src/home/switches.ts):
|
|
81
|
+
* a refresh does not overwrite them until released. */
|
|
82
|
+
held = new Set();
|
|
83
|
+
/** The selected pull request's number. */
|
|
84
|
+
selected = null;
|
|
85
|
+
notice = null;
|
|
86
|
+
tools;
|
|
87
|
+
provider;
|
|
88
|
+
inFlight = null;
|
|
89
|
+
greeted = false;
|
|
90
|
+
pullsWarned = false;
|
|
91
|
+
/** The latest run per PR this home started or re-attached to. */
|
|
92
|
+
runs = new Map();
|
|
93
|
+
/** Quotes by PR, each for the head it priced. */
|
|
94
|
+
quotes = new Map();
|
|
95
|
+
/** The pending rest timer's cancel. */
|
|
96
|
+
pricer = null;
|
|
97
|
+
/** The one background preview in flight. */
|
|
98
|
+
pricing = null;
|
|
99
|
+
timers = new Set();
|
|
100
|
+
/** PRs whose review the person stopped before it had a run id. */
|
|
101
|
+
stopping = new Set();
|
|
102
|
+
attachedTo = null;
|
|
103
|
+
disposed = false;
|
|
104
|
+
/** pi refused the chat for its login: the error it showed. */
|
|
105
|
+
chatLoginError = null;
|
|
106
|
+
/** When the person last signed in again, ms since the epoch. */
|
|
107
|
+
reloggedAt = null;
|
|
108
|
+
after;
|
|
109
|
+
constructor(o) {
|
|
110
|
+
this.o = o;
|
|
111
|
+
this.tools = o.tools;
|
|
112
|
+
this.provider = providerOf(o.model);
|
|
113
|
+
this.after = o.after ?? noTimer;
|
|
114
|
+
if (o.adapters.autoReview.trigger)
|
|
115
|
+
this.state.trigger = PENDING;
|
|
116
|
+
if (o.adapters.loginOwner)
|
|
117
|
+
this.state.loginHealth = PENDING;
|
|
118
|
+
}
|
|
119
|
+
/** The session ended: no timer fires and no preview runs. Runs go on. */
|
|
120
|
+
dispose() {
|
|
121
|
+
this.disposed = true;
|
|
122
|
+
this.pricer?.();
|
|
123
|
+
this.pricer = null;
|
|
124
|
+
this.pricing?.stop.abort();
|
|
125
|
+
this.pricing = null;
|
|
126
|
+
for (const cancel of this.timers)
|
|
127
|
+
cancel();
|
|
128
|
+
this.timers.clear();
|
|
129
|
+
}
|
|
130
|
+
changed() {
|
|
131
|
+
this.o.onChange?.();
|
|
132
|
+
}
|
|
133
|
+
now() {
|
|
134
|
+
return this.o.adapters.clock.now();
|
|
135
|
+
}
|
|
136
|
+
/** The provider the chat's model runs on: the one login that counts. */
|
|
137
|
+
chatProvider() {
|
|
138
|
+
return this.provider;
|
|
139
|
+
}
|
|
140
|
+
/** pi chose another model (at start, or the person's /model). */
|
|
141
|
+
setChatModel(provider) {
|
|
142
|
+
if (provider === undefined || provider === this.provider)
|
|
143
|
+
return;
|
|
144
|
+
this.provider = provider;
|
|
145
|
+
this.changed();
|
|
146
|
+
}
|
|
147
|
+
/** Signed in for the chat's provider: true, false, or null when the auth
|
|
148
|
+
* file could not be read. A login under another provider is not one. */
|
|
149
|
+
signedIn() {
|
|
150
|
+
const l = this.state.login;
|
|
151
|
+
return l.kind === "known" ? l.value.includes(this.provider) : null;
|
|
152
|
+
}
|
|
153
|
+
// ---- one login per box (src/home/loginowner.ts) -------------------------
|
|
154
|
+
/** pi refused the chat for its login (extensions/home.ts reads its error). */
|
|
155
|
+
loginFailed(why) {
|
|
156
|
+
if (this.chatLoginError === why)
|
|
157
|
+
return;
|
|
158
|
+
this.chatLoginError = why;
|
|
159
|
+
this.changed();
|
|
160
|
+
}
|
|
161
|
+
/** pi answered the chat: the login works. */
|
|
162
|
+
chatAnswered() {
|
|
163
|
+
if (this.chatLoginError === null)
|
|
164
|
+
return;
|
|
165
|
+
this.chatLoginError = null;
|
|
166
|
+
this.changed();
|
|
167
|
+
}
|
|
168
|
+
/** The person signed in again: what said the old login failed is past. */
|
|
169
|
+
signedInAgain() {
|
|
170
|
+
this.chatLoginError = null;
|
|
171
|
+
this.reloggedAt = this.now();
|
|
172
|
+
this.changed();
|
|
173
|
+
}
|
|
174
|
+
/** Why the login no longer works ("login expired"), or null while nothing
|
|
175
|
+
* says so. */
|
|
176
|
+
loginExpired() {
|
|
177
|
+
if (this.chatLoginError !== null)
|
|
178
|
+
return "login expired";
|
|
179
|
+
const h = this.state.loginHealth;
|
|
180
|
+
if (h?.kind === "known" &&
|
|
181
|
+
h.value.expired !== undefined &&
|
|
182
|
+
!(this.reloggedAt !== null && (h.value.checkedAt ?? 0) < this.reloggedAt))
|
|
183
|
+
return h.value.expired;
|
|
184
|
+
const a = this.state.autoReview;
|
|
185
|
+
if (a.kind === "known" &&
|
|
186
|
+
a.value.state === "paused-login" &&
|
|
187
|
+
a.value.cause === "login expired" &&
|
|
188
|
+
!(this.reloggedAt !== null && this.now() - this.reloggedAt < RELOGIN_GRACE_MS))
|
|
189
|
+
return "login expired";
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/** Who refreshes the login now; null when that is not known. */
|
|
193
|
+
refreshedBy() {
|
|
194
|
+
const h = this.state.loginHealth;
|
|
195
|
+
return h?.kind === "known" ? h.value.refresher : null;
|
|
196
|
+
}
|
|
197
|
+
/** The setup steps a read showed to be needed: the checklist's gaps. */
|
|
198
|
+
gaps() {
|
|
199
|
+
return this.o.gaps?.() ?? [];
|
|
200
|
+
}
|
|
201
|
+
/** Is anything still being read? */
|
|
202
|
+
busy() {
|
|
203
|
+
return (this.inFlight !== null ||
|
|
204
|
+
this.pricing !== null ||
|
|
205
|
+
[...this.runs.keys()].some((pr) => this.isRunning(pr)));
|
|
206
|
+
}
|
|
207
|
+
repo() {
|
|
208
|
+
const r = valueOf(this.state.repo);
|
|
209
|
+
return typeof r === "string" ? r : null;
|
|
210
|
+
}
|
|
211
|
+
/** The lists, or null when the open pull requests are not known. */
|
|
212
|
+
lists() {
|
|
213
|
+
const pulls = valueOf(this.state.pulls);
|
|
214
|
+
if (pulls === undefined)
|
|
215
|
+
return null;
|
|
216
|
+
const sorted = [...pulls].sort(newestFirst);
|
|
217
|
+
if (valueOf(this.state.reviews) === undefined)
|
|
218
|
+
return { toReview: sorted, reviewed: null };
|
|
219
|
+
return {
|
|
220
|
+
toReview: sorted.filter((p) => this.reviewOf(p.number) === undefined),
|
|
221
|
+
reviewed: sorted.flatMap((p) => {
|
|
222
|
+
const review = this.reviewOf(p.number);
|
|
223
|
+
return review ? [{ pull: p, review }] : [];
|
|
224
|
+
}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** Why auto-review's half of the Reviewed list is unknown; null when known. */
|
|
228
|
+
autoUnknown() {
|
|
229
|
+
const a = this.state.autoRounds;
|
|
230
|
+
return a.kind === "unknown" ? a.why : null;
|
|
231
|
+
}
|
|
232
|
+
/** Every listed pull request, in the panel's order: To review, then Reviewed. */
|
|
233
|
+
ordered() {
|
|
234
|
+
const l = this.lists();
|
|
235
|
+
return l ? [...l.toReview, ...(l.reviewed ?? []).map((r) => r.pull)] : [];
|
|
236
|
+
}
|
|
237
|
+
selectedPull() {
|
|
238
|
+
return this.ordered().find((p) => p.number === this.selected);
|
|
239
|
+
}
|
|
240
|
+
/** The latest review of `pr`: this home's finished run, the person's own
|
|
241
|
+
* reviews, or auto-review's, whichever finished last. */
|
|
242
|
+
reviewOf(pr) {
|
|
243
|
+
const run = this.runs.get(pr);
|
|
244
|
+
const mine = run?.stage === "done" ? runReview(run) : undefined;
|
|
245
|
+
return later(later(valueOf(this.state.reviews)?.get(pr), valueOf(this.state.autoRounds)?.get(pr)), mine);
|
|
246
|
+
}
|
|
247
|
+
/** Is a review of `pr` under way (pricing, starting or running)? */
|
|
248
|
+
isRunning(pr) {
|
|
249
|
+
const r = this.runs.get(pr);
|
|
250
|
+
return r !== undefined && r.stage !== "done" && r.stage !== "failed";
|
|
251
|
+
}
|
|
252
|
+
/** The PR's price, for its head: pending while it is being priced, null
|
|
253
|
+
* before it was asked for. */
|
|
254
|
+
quoteOf(p) {
|
|
255
|
+
const e = this.quotes.get(p.number);
|
|
256
|
+
return e !== undefined && e.sha === p.headSha ? e.q : null;
|
|
257
|
+
}
|
|
258
|
+
say(text, kind = "info") {
|
|
259
|
+
this.notice = { text, kind };
|
|
260
|
+
this.changed();
|
|
261
|
+
}
|
|
262
|
+
// ---- the actions ------------------------------------------------------
|
|
263
|
+
/** Read every source again. A refresh already running is joined. */
|
|
264
|
+
refresh() {
|
|
265
|
+
this.inFlight ??= this.detect().finally(() => {
|
|
266
|
+
this.inFlight = null;
|
|
267
|
+
this.changed();
|
|
268
|
+
});
|
|
269
|
+
return this.inFlight;
|
|
270
|
+
}
|
|
271
|
+
async detect() {
|
|
272
|
+
const a = this.o.adapters;
|
|
273
|
+
const land = (key) => (v) => {
|
|
274
|
+
if (this.held.has(key))
|
|
275
|
+
return;
|
|
276
|
+
this.state[key] = v;
|
|
277
|
+
this.changed();
|
|
278
|
+
};
|
|
279
|
+
const side = Promise.all([
|
|
280
|
+
safe(() => a.login.providers()).then(land("login")),
|
|
281
|
+
safe(() => a.github.user()).then(land("github")),
|
|
282
|
+
safe(() => a.autoReview.status()).then(land("autoReview")),
|
|
283
|
+
safe(() => a.memory.status()).then(land("memory")),
|
|
284
|
+
a.autoReview.trigger
|
|
285
|
+
? safe(() => a.autoReview.trigger()).then(land("trigger"))
|
|
286
|
+
: Promise.resolve(),
|
|
287
|
+
safe(() => a.spend.totals()).then(land("spend")),
|
|
288
|
+
a.loginOwner
|
|
289
|
+
? safe(() => a.loginOwner.health()).then(land("loginHealth"))
|
|
290
|
+
: Promise.resolve(),
|
|
291
|
+
]);
|
|
292
|
+
const repo = await safe(() => a.repo.repo());
|
|
293
|
+
land("repo")(repo);
|
|
294
|
+
if (repo.kind === "unknown") {
|
|
295
|
+
const why = `can't read the repository from your qcr config: ${repo.why}`;
|
|
296
|
+
this.state.pulls = unknown(why);
|
|
297
|
+
this.state.reviews = unknown(why);
|
|
298
|
+
this.state.autoRounds = unknown(why);
|
|
299
|
+
}
|
|
300
|
+
else if (repo.value === null) {
|
|
301
|
+
this.state.pulls = unknown("no repository is chosen");
|
|
302
|
+
this.state.reviews = unknown("no repository is chosen");
|
|
303
|
+
this.state.autoRounds = unknown("no repository is chosen");
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
const name = repo.value;
|
|
307
|
+
const [pulls, reviews, autoRounds] = await Promise.all([
|
|
308
|
+
safe(() => a.github.openPulls(name)),
|
|
309
|
+
safe(() => a.reviews.latest(name)),
|
|
310
|
+
safe(() => a.reviews.autoRounds(name)),
|
|
311
|
+
this.attach(name),
|
|
312
|
+
]);
|
|
313
|
+
this.state.pulls = pulls;
|
|
314
|
+
this.state.reviews = reviews;
|
|
315
|
+
this.state.autoRounds = autoRounds;
|
|
316
|
+
}
|
|
317
|
+
this.keepSelection();
|
|
318
|
+
await side;
|
|
319
|
+
// the checklist never throws (a step's detect that does is a missing)
|
|
320
|
+
await this.o.onRead?.();
|
|
321
|
+
this.greet();
|
|
322
|
+
return this.summary();
|
|
323
|
+
}
|
|
324
|
+
/** The selection survives a refresh while its PR is listed; else the first row. */
|
|
325
|
+
keepSelection() {
|
|
326
|
+
const rows = this.ordered();
|
|
327
|
+
if (!rows.some((p) => p.number === this.selected))
|
|
328
|
+
this.selected = rows[0]?.number ?? null;
|
|
329
|
+
this.rested();
|
|
330
|
+
}
|
|
331
|
+
/** The first notice, once everything has been read. */
|
|
332
|
+
greet() {
|
|
333
|
+
const repo = this.repo();
|
|
334
|
+
if (this.state.pulls.kind === "unknown" && repo !== null) {
|
|
335
|
+
this.pullsWarned = true;
|
|
336
|
+
this.say(`Couldn't read ${repo}'s pull requests: ${this.state.pulls.why}.`, "warn");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (this.pullsWarned) {
|
|
340
|
+
// the list came back: the warning about it no longer holds
|
|
341
|
+
this.pullsWarned = false;
|
|
342
|
+
const open = this.ordered().length;
|
|
343
|
+
this.say(`${open} open pull request${open === 1 ? "" : "s"}.`, "ok");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (this.greeted)
|
|
347
|
+
return;
|
|
348
|
+
this.greeted = true;
|
|
349
|
+
const gaps = this.gaps();
|
|
350
|
+
if (gaps.length > 0) {
|
|
351
|
+
this.say("Not set up yet: the steps above say what is missing.", "warn");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const user = nullable(this.state.github);
|
|
355
|
+
const found = [
|
|
356
|
+
this.signedIn() ? `your ${providerLabel(this.provider)} login` : null,
|
|
357
|
+
user ? `GitHub @${user}` : null,
|
|
358
|
+
repo ? `${repo} from your qcr config` : null,
|
|
359
|
+
].filter((x) => x !== null);
|
|
360
|
+
const open = this.ordered().length;
|
|
361
|
+
this.say(found.length === 3
|
|
362
|
+
? `Found ${found[0]}, ${found[1]} and ${found[2]}: you're set. ${open} open pull request${open === 1 ? "" : "s"}.`
|
|
363
|
+
: `${open} open pull request${open === 1 ? "" : "s"}.`, "ok");
|
|
364
|
+
}
|
|
365
|
+
summary() {
|
|
366
|
+
const l = this.lists();
|
|
367
|
+
if (l === null) {
|
|
368
|
+
const p = this.state.pulls;
|
|
369
|
+
return p.kind === "unknown" ? `pull requests unknown: ${p.why}` : "still reading";
|
|
370
|
+
}
|
|
371
|
+
return `${l.toReview.length} to review, ${l.reviewed === null ? "reviewed unknown" : `${l.reviewed.length} reviewed`}`;
|
|
372
|
+
}
|
|
373
|
+
/** Move the selection by `by` rows through both lists. */
|
|
374
|
+
move(by) {
|
|
375
|
+
const rows = this.ordered();
|
|
376
|
+
if (rows.length === 0)
|
|
377
|
+
return "there is no pull request to move to";
|
|
378
|
+
const at = rows.findIndex((p) => p.number === this.selected);
|
|
379
|
+
const next = rows[Math.max(0, Math.min(rows.length - 1, (at < 0 ? 0 : at) + Math.trunc(by)))];
|
|
380
|
+
this.selected = next.number;
|
|
381
|
+
this.rested();
|
|
382
|
+
this.changed();
|
|
383
|
+
return `selected #${next.number} ${next.title}`;
|
|
384
|
+
}
|
|
385
|
+
/** Select one listed pull request (a hover or a click does this). */
|
|
386
|
+
select(pr) {
|
|
387
|
+
const p = this.ordered().find((x) => x.number === pr);
|
|
388
|
+
if (!p) {
|
|
389
|
+
const repo = this.repo();
|
|
390
|
+
return `#${pr} is not listed: it is not an open pull request${repo ? ` in ${repo}` : ""}, or the list is not known`;
|
|
391
|
+
}
|
|
392
|
+
if (this.selected !== pr) {
|
|
393
|
+
this.selected = pr;
|
|
394
|
+
this.rested();
|
|
395
|
+
this.changed();
|
|
396
|
+
}
|
|
397
|
+
return `selected #${p.number} ${p.title}`;
|
|
398
|
+
}
|
|
399
|
+
/** Open a pull request (by default the selected one) in the browser. */
|
|
400
|
+
async openPr(pr) {
|
|
401
|
+
const repo = this.repo();
|
|
402
|
+
if (repo === null) {
|
|
403
|
+
const r = this.state.repo;
|
|
404
|
+
return r.kind === "unknown"
|
|
405
|
+
? `the repository is unknown (${r.why}), so there is nothing to open`
|
|
406
|
+
: "no repository is chosen, so there is nothing to open";
|
|
407
|
+
}
|
|
408
|
+
const n = pr ?? this.selected;
|
|
409
|
+
if (n === null)
|
|
410
|
+
return "no pull request is selected";
|
|
411
|
+
const url = pullUrl(repo, n);
|
|
412
|
+
const r = await this.o.adapters.github
|
|
413
|
+
.openInBrowser(repo, n)
|
|
414
|
+
.catch((err) => ({ ok: false, why: message(err) }));
|
|
415
|
+
if (r.ok) {
|
|
416
|
+
this.say(`Opened #${n} in your browser.`, "ok");
|
|
417
|
+
return `opened ${url} in the browser`;
|
|
418
|
+
}
|
|
419
|
+
this.say(`Couldn't open a browser (${r.why}). The pull request is at ${url}`, "warn");
|
|
420
|
+
return `couldn't open a browser (${r.why}); the pull request is at ${url}`;
|
|
421
|
+
}
|
|
422
|
+
// ---- the review button ------------------------------------------------
|
|
423
|
+
/** A PR's price for the model: null until it is quoted. */
|
|
424
|
+
priceOf(p) {
|
|
425
|
+
const q = this.quoteOf(p);
|
|
426
|
+
if (q === null || q.kind === "pending")
|
|
427
|
+
return null;
|
|
428
|
+
return q.kind === "known"
|
|
429
|
+
? { estimate_usd: q.value.estimateUsd, cap_usd: q.value.capUsd }
|
|
430
|
+
: { unknown_because: q.why };
|
|
431
|
+
}
|
|
432
|
+
pullOf(pr) {
|
|
433
|
+
return this.ordered().find((p) => p.number === pr);
|
|
434
|
+
}
|
|
435
|
+
/** The cursor came to rest on a PR: price it in a moment, unless its head
|
|
436
|
+
* is priced already. A rest on another PR first cancels this one's. */
|
|
437
|
+
rested() {
|
|
438
|
+
this.pricer?.();
|
|
439
|
+
this.pricer = null;
|
|
440
|
+
const p = this.selectedPull();
|
|
441
|
+
if (this.disposed || !p || this.quoteOf(p) !== null || this.isRunning(p.number))
|
|
442
|
+
return;
|
|
443
|
+
this.pricer = this.after(PRICE_REST_MS, () => {
|
|
444
|
+
this.pricer = null;
|
|
445
|
+
void this.priceSelected();
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/** Give way: the background preview in flight stops, and its PR is left
|
|
449
|
+
* unpriced (the next rest on it prices it again). */
|
|
450
|
+
yieldPricing() {
|
|
451
|
+
const p = this.pricing;
|
|
452
|
+
if (p === null)
|
|
453
|
+
return;
|
|
454
|
+
p.stop.abort();
|
|
455
|
+
this.pricing = null;
|
|
456
|
+
if (this.quotes.get(p.pr)?.q.kind === "pending")
|
|
457
|
+
this.quotes.delete(p.pr);
|
|
458
|
+
}
|
|
459
|
+
/** Price the PR the cursor rests on: one preview at a time. */
|
|
460
|
+
async priceSelected() {
|
|
461
|
+
const repo = this.repo();
|
|
462
|
+
const p = this.selectedPull();
|
|
463
|
+
if (repo === null || !p || this.quoteOf(p) !== null || this.isRunning(p.number))
|
|
464
|
+
return;
|
|
465
|
+
this.yieldPricing();
|
|
466
|
+
const stop = new AbortController();
|
|
467
|
+
this.pricing = { pr: p.number, stop };
|
|
468
|
+
this.quotes.set(p.number, { sha: p.headSha, q: PENDING });
|
|
469
|
+
this.changed();
|
|
470
|
+
const r = await attempt(() => this.o.adapters.reviews.preview(repo, p.number, { signal: stop.signal }));
|
|
471
|
+
if (this.pricing?.stop === stop)
|
|
472
|
+
this.pricing = null;
|
|
473
|
+
if (stop.signal.aborted)
|
|
474
|
+
return;
|
|
475
|
+
this.quotes.set(p.number, { sha: p.headSha, q: r.ok ? known(r.quote) : unknown(r.why) });
|
|
476
|
+
this.changed();
|
|
477
|
+
}
|
|
478
|
+
/** ⏎ (or alt+r): review the selected PR, or show its review when it has
|
|
479
|
+
* one and none is running. */
|
|
480
|
+
enter() {
|
|
481
|
+
const p = this.selectedPull();
|
|
482
|
+
if (p && !this.isRunning(p.number) && this.reviewOf(p.number))
|
|
483
|
+
return Promise.resolve(this.showReview(p.number));
|
|
484
|
+
return this.review();
|
|
485
|
+
}
|
|
486
|
+
/** Refused before anything starts: why the review button cannot act. */
|
|
487
|
+
cannotAct(pr) {
|
|
488
|
+
const repo = this.repo();
|
|
489
|
+
if (repo === null) {
|
|
490
|
+
const r = this.state.repo;
|
|
491
|
+
return r.kind === "unknown"
|
|
492
|
+
? `the repository is unknown (${r.why}), so there is nothing to review`
|
|
493
|
+
: "no repository is chosen, so there is nothing to review";
|
|
494
|
+
}
|
|
495
|
+
const gaps = this.gaps();
|
|
496
|
+
if (gaps.length > 0)
|
|
497
|
+
return `finish setup first: ${gaps.join(", ")}`;
|
|
498
|
+
const n = pr ?? this.selected;
|
|
499
|
+
if (n === null)
|
|
500
|
+
return "no pull request is selected";
|
|
501
|
+
return (this.pullOf(n) ??
|
|
502
|
+
`#${n} is not listed: it is not an open pull request in ${repo}, or the list is not known`);
|
|
503
|
+
}
|
|
504
|
+
/** Review a pull request (by default the selected one) in the background:
|
|
505
|
+
* preview it, start a detached run with the preview's key, follow it. The
|
|
506
|
+
* run writes its report to a file and never posts by itself. */
|
|
507
|
+
async review(pr) {
|
|
508
|
+
const target = this.cannotAct(pr);
|
|
509
|
+
if (typeof target === "string")
|
|
510
|
+
return target;
|
|
511
|
+
const repo = this.repo();
|
|
512
|
+
const n = target.number;
|
|
513
|
+
const running = this.runs.get(n);
|
|
514
|
+
if (running && this.isRunning(n))
|
|
515
|
+
return `#${n} is already being reviewed${running.runId ? ` (run ${running.runId})` : ""}`;
|
|
516
|
+
const run = plannedRun(n, this.now());
|
|
517
|
+
this.runs.set(n, run);
|
|
518
|
+
this.stopping.delete(n);
|
|
519
|
+
this.selected = n;
|
|
520
|
+
this.pricer?.();
|
|
521
|
+
this.pricer = null;
|
|
522
|
+
this.yieldPricing();
|
|
523
|
+
this.say(`Pricing the review of #${n}…`);
|
|
524
|
+
const fail = (why) => {
|
|
525
|
+
run.stage = "failed";
|
|
526
|
+
run.error = why;
|
|
527
|
+
run.ended = this.now();
|
|
528
|
+
this.say(`#${n} was not reviewed: ${why}. It can be reviewed again.`, "warn");
|
|
529
|
+
return `the review of #${n} did not start: ${why}`;
|
|
530
|
+
};
|
|
531
|
+
const reviews = this.o.adapters.reviews;
|
|
532
|
+
const pre = await attempt(() => reviews.preview(repo, n));
|
|
533
|
+
this.quotes.set(n, { sha: target.headSha, q: pre.ok ? known(pre.quote) : unknown(pre.why) });
|
|
534
|
+
if (!pre.ok)
|
|
535
|
+
return fail(`the preview failed: ${pre.why}`);
|
|
536
|
+
if (this.stopping.delete(n))
|
|
537
|
+
return fail("you stopped it");
|
|
538
|
+
quoteRun(run, pre.quote);
|
|
539
|
+
run.now = "starting the review";
|
|
540
|
+
this.changed();
|
|
541
|
+
const st = await attempt(() => reviews.start(repo, n, pre.quote.key));
|
|
542
|
+
if (!st.ok)
|
|
543
|
+
return fail(st.why);
|
|
544
|
+
run.runId = st.runId;
|
|
545
|
+
run.report = st.report;
|
|
546
|
+
run.now = "fetching the pull request";
|
|
547
|
+
this.say(`Reviewing #${n} in the background — ${priceWords(run.est, run.cap)}. Keep chatting.`);
|
|
548
|
+
if (this.stopping.delete(n))
|
|
549
|
+
void this.stopReview(n);
|
|
550
|
+
this.follow(run);
|
|
551
|
+
return `the review of #${n} started (run ${st.runId}, ${priceWords(run.est, run.cap)}); it writes its report to ${st.report} and posts nothing by itself`;
|
|
552
|
+
}
|
|
553
|
+
/** Poll a run's status until it is over. */
|
|
554
|
+
follow(run) {
|
|
555
|
+
if (this.disposed)
|
|
556
|
+
return;
|
|
557
|
+
const cancel = this.after(STATUS_POLL_MS, () => {
|
|
558
|
+
this.timers.delete(cancel);
|
|
559
|
+
void this.poll(run);
|
|
560
|
+
});
|
|
561
|
+
this.timers.add(cancel);
|
|
562
|
+
}
|
|
563
|
+
async poll(run) {
|
|
564
|
+
if (this.disposed || run.runId === undefined)
|
|
565
|
+
return;
|
|
566
|
+
const id = run.runId;
|
|
567
|
+
const s = await safe(() => this.o.adapters.reviews.status(id));
|
|
568
|
+
if (this.disposed || this.runs.get(run.pr) !== run)
|
|
569
|
+
return;
|
|
570
|
+
if (s.kind === "unknown") {
|
|
571
|
+
run.now = `can't read its status: ${s.why}`;
|
|
572
|
+
this.changed();
|
|
573
|
+
return this.follow(run);
|
|
574
|
+
}
|
|
575
|
+
const over = foldStatus(run, s.value, this.now());
|
|
576
|
+
this.changed();
|
|
577
|
+
if (!over)
|
|
578
|
+
return this.follow(run);
|
|
579
|
+
await this.finished(run);
|
|
580
|
+
}
|
|
581
|
+
/** A run this home followed is over: say how, then the posting rule. */
|
|
582
|
+
async finished(run) {
|
|
583
|
+
const n = run.pr;
|
|
584
|
+
if (run.stage === "failed") {
|
|
585
|
+
this.say(`#${n} was not reviewed: ${run.error ?? "unknown"}. It can be reviewed again.`, "warn");
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const high = run.findings.filter((f) => f.sev === "high").length;
|
|
589
|
+
this.selected = n;
|
|
590
|
+
this.say(`#${n} reviewed — ${plural(run.findings.length, "finding")}${high ? `, ${high} high` : ""}. ${"⏎"} shows them.`, "ok");
|
|
591
|
+
await this.offerPost(run);
|
|
592
|
+
}
|
|
593
|
+
/** After a finished review the person's posting rule applies: never, always,
|
|
594
|
+
* or ask with "No, keep it here" first. Nobody to ask is a no. */
|
|
595
|
+
async offerPost(run) {
|
|
596
|
+
const file = run.postFile;
|
|
597
|
+
const repo = this.repo();
|
|
598
|
+
if (file === undefined || repo === null)
|
|
599
|
+
return;
|
|
600
|
+
const rule = this.o.postingRule?.() ?? "ask";
|
|
601
|
+
const n = run.pr;
|
|
602
|
+
if (rule === "never")
|
|
603
|
+
return;
|
|
604
|
+
if (rule === "always") {
|
|
605
|
+
this.say(`Posting the review of #${n} to GitHub, as your posting rule says…`);
|
|
606
|
+
return this.postNow(n, file);
|
|
607
|
+
}
|
|
608
|
+
const ask = this.o.ask;
|
|
609
|
+
if (ask === undefined)
|
|
610
|
+
return;
|
|
611
|
+
// the review first, so the question is about something the person has read
|
|
612
|
+
this.showReview(n);
|
|
613
|
+
const dry = await safe(() => this.o.adapters.reviews.dryRun());
|
|
614
|
+
const yes = dry.kind === "known"
|
|
615
|
+
? postAnswer(dry.value)
|
|
616
|
+
: "Yes, post it as a comment-only review (your install's dry_run decides whether it is sent)";
|
|
617
|
+
let picked;
|
|
618
|
+
try {
|
|
619
|
+
picked = await ask(`Post the review of #${n} (${repo}) on GitHub as a comment-only review?`, [
|
|
620
|
+
KEEP_IT_HERE,
|
|
621
|
+
yes,
|
|
622
|
+
]);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
picked = undefined;
|
|
626
|
+
}
|
|
627
|
+
if (picked !== yes) {
|
|
628
|
+
this.say(`The review of #${n} stays here: nothing was posted.`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
await this.postNow(n, file);
|
|
632
|
+
}
|
|
633
|
+
async postNow(n, file) {
|
|
634
|
+
const r = await attempt(() => this.o.adapters.reviews.post(file));
|
|
635
|
+
this.say(r.ok ? r.line : `The review of #${n} was not posted: ${r.why}`, r.ok ? "ok" : "warn");
|
|
636
|
+
}
|
|
637
|
+
/** Put a reviewed PR's findings in the chat as a message. */
|
|
638
|
+
showReview(pr) {
|
|
639
|
+
const n = pr ?? this.selected;
|
|
640
|
+
if (n === null)
|
|
641
|
+
return "no pull request is selected";
|
|
642
|
+
const review = this.reviewOf(n);
|
|
643
|
+
if (!review)
|
|
644
|
+
return `#${n} has no review yet`;
|
|
645
|
+
const title = this.pullOf(n)?.title ?? "";
|
|
646
|
+
const content = reviewMessage({ pr: n, title, review });
|
|
647
|
+
if (!this.o.send)
|
|
648
|
+
return content;
|
|
649
|
+
this.o.send({
|
|
650
|
+
content,
|
|
651
|
+
details: { repo: this.repo(), pr: n, by: review.by, findings: review.list ?? null },
|
|
652
|
+
});
|
|
653
|
+
return `the review of #${n} is in the chat`;
|
|
654
|
+
}
|
|
655
|
+
/** Stop the review of a pull request (by default the selected one). The PR
|
|
656
|
+
* is reviewable again once its status says it stopped. */
|
|
657
|
+
async stopReview(pr) {
|
|
658
|
+
const n = pr ?? this.selected;
|
|
659
|
+
if (n === null)
|
|
660
|
+
return "no pull request is selected";
|
|
661
|
+
const run = this.runs.get(n);
|
|
662
|
+
if (!run || !this.isRunning(n))
|
|
663
|
+
return `no review of #${n} is running`;
|
|
664
|
+
if (run.runId === undefined) {
|
|
665
|
+
// still pricing or starting: it stops as soon as it has a run
|
|
666
|
+
this.stopping.add(n);
|
|
667
|
+
this.say(`Stopping the review of #${n}…`);
|
|
668
|
+
return `the review of #${n} stops as soon as it starts`;
|
|
669
|
+
}
|
|
670
|
+
const r = await attempt(() => this.o.adapters.reviews.stop(run.runId));
|
|
671
|
+
if (!r.ok) {
|
|
672
|
+
this.say(`Couldn't stop the review of #${n}: ${r.why}`, "warn");
|
|
673
|
+
return `couldn't stop the review of #${n}: ${r.why}`;
|
|
674
|
+
}
|
|
675
|
+
this.say(`Stopping the review of #${n}…`);
|
|
676
|
+
return `stopped the review of #${n} (run ${run.runId})`;
|
|
677
|
+
}
|
|
678
|
+
/** On opening (and on a new repository): the registry's runs of `repo`.
|
|
679
|
+
* A running one is followed, a finished or failed one shown; nothing is
|
|
680
|
+
* asked about a run that ended while the home was closed. */
|
|
681
|
+
async attach(repo) {
|
|
682
|
+
if (this.attachedTo === repo)
|
|
683
|
+
return;
|
|
684
|
+
this.attachedTo = repo;
|
|
685
|
+
const r = await safe(() => this.o.adapters.reviews.runs(repo));
|
|
686
|
+
if (r.kind === "unknown" || this.disposed)
|
|
687
|
+
return;
|
|
688
|
+
const latest = new Map();
|
|
689
|
+
for (const s of r.value)
|
|
690
|
+
if (s.pr !== null && s.repo === repo)
|
|
691
|
+
latest.set(s.pr, s);
|
|
692
|
+
for (const [pr, s] of latest) {
|
|
693
|
+
if (this.runs.has(pr))
|
|
694
|
+
continue;
|
|
695
|
+
const run = attachedRun(s);
|
|
696
|
+
foldStatus(run, s, this.now());
|
|
697
|
+
this.runs.set(pr, run);
|
|
698
|
+
if (s.state === "running")
|
|
699
|
+
this.follow(run);
|
|
700
|
+
}
|
|
701
|
+
this.changed();
|
|
702
|
+
}
|
|
703
|
+
// ---- the switches (src/home/switches.ts) ------------------------------
|
|
704
|
+
/** Can the home switch auto-review / memory, or only show them? */
|
|
705
|
+
canSwitch(what) {
|
|
706
|
+
return this.o.adapters[what].control !== undefined;
|
|
707
|
+
}
|
|
708
|
+
/** Memory's switch, when the home has one. */
|
|
709
|
+
memoryControl() {
|
|
710
|
+
return this.o.adapters.memory.control;
|
|
711
|
+
}
|
|
712
|
+
/** The trigger label, when known. */
|
|
713
|
+
trigger() {
|
|
714
|
+
const t = this.state.trigger;
|
|
715
|
+
return t?.kind === "known" ? t.value : null;
|
|
716
|
+
}
|
|
717
|
+
switchHost() {
|
|
718
|
+
const hold = (slot, v) => {
|
|
719
|
+
this.state[slot] = v;
|
|
720
|
+
this.held.add(slot);
|
|
721
|
+
this.changed();
|
|
722
|
+
};
|
|
723
|
+
return {
|
|
724
|
+
autoReview: () => this.state.autoReview,
|
|
725
|
+
memory: () => this.state.memory,
|
|
726
|
+
trigger: () => this.trigger(),
|
|
727
|
+
repo: () => this.repo(),
|
|
728
|
+
selected: () => this.selected,
|
|
729
|
+
say: (text, kind) => this.say(text, kind),
|
|
730
|
+
hold,
|
|
731
|
+
release: (slot) => {
|
|
732
|
+
this.held.delete(slot);
|
|
733
|
+
},
|
|
734
|
+
refresh: () => this.refresh(),
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
/** Switch auto-review on or off: every question asked through `ask`. */
|
|
738
|
+
switchAutoReview(ask) {
|
|
739
|
+
return switchAutoReview(this.switchHost(), this.o.adapters, ask);
|
|
740
|
+
}
|
|
741
|
+
/** Switch memory on or off: its own switch, auto-review untouched. */
|
|
742
|
+
switchMemory(ask) {
|
|
743
|
+
return switchMemory(this.switchHost(), this.o.adapters, ask);
|
|
744
|
+
}
|
|
745
|
+
/** The login's owner and health, for the model: null when unknown. */
|
|
746
|
+
loginOwnerStatus(h) {
|
|
747
|
+
const expired = this.loginExpired();
|
|
748
|
+
return {
|
|
749
|
+
refreshed_by: this.refreshedBy(),
|
|
750
|
+
expired: expired !== null ? true : h.kind === "known" ? false : null,
|
|
751
|
+
...(h.kind === "unknown" ? { refresher_unknown_because: h.why } : {}),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
/** What the panel shows, for the model: an unknown source is null, never
|
|
755
|
+
* absent and never zero. */
|
|
756
|
+
status() {
|
|
757
|
+
const l = this.lists();
|
|
758
|
+
const why = (s) => (s.kind === "unknown" ? s.why : null);
|
|
759
|
+
const auto = this.state.autoReview;
|
|
760
|
+
return {
|
|
761
|
+
repo: nullable(this.state.repo),
|
|
762
|
+
login: {
|
|
763
|
+
provider: this.provider,
|
|
764
|
+
signed_in: this.signedIn(),
|
|
765
|
+
...(why(this.state.login) ? { unknown_because: why(this.state.login) } : {}),
|
|
766
|
+
...(this.state.loginHealth ? this.loginOwnerStatus(this.state.loginHealth) : {}),
|
|
767
|
+
},
|
|
768
|
+
github: {
|
|
769
|
+
user: nullable(this.state.github),
|
|
770
|
+
signed_in: this.state.github.kind === "known" ? this.state.github.value !== null : null,
|
|
771
|
+
},
|
|
772
|
+
tools: this.tools,
|
|
773
|
+
setup_needed: this.gaps(),
|
|
774
|
+
auto_review: auto.kind === "known" ? auto.value : null,
|
|
775
|
+
...(auto.kind === "unknown" ? { auto_review_unknown_because: auto.why } : {}),
|
|
776
|
+
...(this.state.trigger
|
|
777
|
+
? { auto_review_picks_up: { trigger_label: this.trigger(), review_requests: true } }
|
|
778
|
+
: {}),
|
|
779
|
+
memory: nullable(this.state.memory),
|
|
780
|
+
spend: nullable(this.state.spend),
|
|
781
|
+
to_review: l ? l.toReview.map((p) => ({ ...row(p), price: this.priceOf(p) })) : null,
|
|
782
|
+
reviewed: l?.reviewed ? l.reviewed.map((r) => ({ ...row(r.pull), review: r.review })) : null,
|
|
783
|
+
...(l?.reviewed && this.autoUnknown() !== null
|
|
784
|
+
? { auto_reviews_unknown_because: this.autoUnknown() }
|
|
785
|
+
: {}),
|
|
786
|
+
reviewing: [...this.runs.values()]
|
|
787
|
+
.filter((r) => this.isRunning(r.pr))
|
|
788
|
+
.map((r) => ({
|
|
789
|
+
number: r.pr,
|
|
790
|
+
run_id: r.runId ?? null,
|
|
791
|
+
stage: r.stage,
|
|
792
|
+
estimate_usd: r.est,
|
|
793
|
+
cap_usd: r.cap,
|
|
794
|
+
lenses: r.lenses.map((x) => ({ name: x.name, state: x.state, tools: x.tools })),
|
|
795
|
+
})),
|
|
796
|
+
...(why(this.state.pulls) ? { pull_requests_unknown_because: why(this.state.pulls) } : {}),
|
|
797
|
+
selected: this.selected,
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function row(p) {
|
|
802
|
+
return {
|
|
803
|
+
number: p.number,
|
|
804
|
+
title: p.title,
|
|
805
|
+
author: p.author,
|
|
806
|
+
updated_at: p.updatedAt,
|
|
807
|
+
draft: p.isDraft,
|
|
808
|
+
branch: p.headRefName,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
//# sourceMappingURL=model.js.map
|