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