@quickchatai/quickreview 1.2.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -53
- package/bin/qcr.js +1 -1
- package/config.example.toml +25 -7
- 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/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 +1 -1
- package/dist/extensions/login-route.js.map +1 -1
- package/dist/extensions/onboarding.js +8 -7
- 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/extensions/vendor/openviking-pi/index.js +4 -0
- package/dist/extensions/vendor/openviking-pi/index.js.map +1 -1
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +3 -1
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -1
- package/dist/extensions/vendor/openviking-pi/sync.js +81 -0
- package/dist/extensions/vendor/openviking-pi/sync.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/cli.js +14 -50
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +16 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/daemon.js +33 -4
- 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 +1 -0
- package/dist/src/debugroundpi.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/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/ledgercheck.js +39 -10
- package/dist/src/ledgercheck.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/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/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 +42 -12
- package/dist/src/npmverbs.js.map +1 -1
- package/dist/src/onboard/choices.js +6 -3
- package/dist/src/onboard/choices.js.map +1 -1
- package/dist/src/onboard/engine.js +33 -292
- package/dist/src/onboard/engine.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/configfill.js +18 -7
- package/dist/src/onboard/steps/configfill.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 +14 -2
- package/dist/src/onboard/steps/memory.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/toolnames.js +2 -2
- package/dist/src/onboard/toolnames.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/qcrbin.js +16 -11
- package/dist/src/qcrbin.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/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/session-command-supervisor.js +38 -9
- package/dist/src/session-command-supervisor.js.map +1 -1
- 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 +75 -9
- package/dist/src/state.js.map +1 -1
- package/dist/src/takeover.js +1 -0
- package/dist/src/takeover.js.map +1 -1
- package/dist/src/takeoverpi.js +2 -1
- package/dist/src/takeoverpi.js.map +1 -1
- package/dist/src/tui/alerts.js +4 -1
- package/dist/src/tui/alerts.js.map +1 -1
- package/dist/src/tui/board.js +2 -2
- package/dist/src/tui/board.js.map +1 -1
- package/dist/src/tui/companion.js +2 -1
- package/dist/src/tui/companion.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/index.js +9 -6
- package/dist/src/tui/index.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/remote.js +1 -1
- package/dist/src/tui/remote.js.map +1 -1
- package/dist/src/tui/route.js +2 -2
- package/dist/src/tui/route.js.map +1 -1
- package/dist/src/tui/setupstate.js +8 -5
- package/dist/src/tui/setupstate.js.map +1 -1
- package/dist/src/tui/sshtriage.js +1 -1
- package/dist/src/tui/sshtriage.js.map +1 -1
- package/dist/src/verdicts.js +12 -0
- package/dist/src/verdicts.js.map +1 -1
- package/dist/src/wire.js +9 -4
- package/dist/src/wire.js.map +1 -1
- package/dist/src/workerbreaker.js +32 -5
- package/dist/src/workerbreaker.js.map +1 -1
- package/dist/src/workerworld.js +4 -0
- package/dist/src/workerworld.js.map +1 -1
- package/docker/README.md +1 -1
- package/docker/entrypoint.sh +40 -6
- package/docker-compose.yml +16 -1
- package/extensions/cli/home.ts +14 -0
- package/extensions/cli/selfimprove.ts +18 -0
- package/extensions/home.ts +484 -0
- package/extensions/login-route.ts +1 -1
- package/extensions/onboarding.ts +8 -7
- package/extensions/selfimprove.ts +65 -0
- package/extensions/setup-scan.ts +79 -766
- package/extensions/setup.ts +14 -70
- package/extensions/vendor/openviking-pi/NOTICE +5 -0
- package/extensions/vendor/openviking-pi/README.md +39 -5
- package/extensions/vendor/openviking-pi/index.ts +4 -0
- package/extensions/vendor/openviking-pi/patches/0014-one-turn-sync-in-flight.patch +239 -0
- package/extensions/vendor/openviking-pi/qcr-hooks.ts +3 -1
- package/extensions/vendor/openviking-pi/sync.ts +80 -0
- package/extensions/vendor/openviking-pi/upstream.json +2 -1
- package/package.json +6 -4
- package/prompts/onboarding.md +8 -7
- package/schemas/review-result.v1.json +60 -0
- package/scripts/deploy.sh +8 -6
- 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/cli.ts +13 -54
- package/src/config.ts +38 -0
- package/src/daemon.ts +34 -4
- package/src/debuground.ts +3 -0
- package/src/debugroundpi.ts +1 -0
- package/src/engine/caserunner-sdk.ts +29 -314
- 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/ledgercheck.ts +45 -9
- package/src/live/publish.ts +4 -1
- package/src/livecli.ts +6 -1
- package/src/loginrefresh/handover.ts +138 -0
- package/src/loginrefresh/lock.ts +72 -3
- package/src/loginrefresh/refresher.ts +34 -5
- package/src/loginrefresh/supervisor.ts +25 -2
- package/src/npmverbs.ts +51 -11
- package/src/onboard/choices.ts +6 -3
- package/src/onboard/engine.ts +64 -434
- package/src/onboard/steps/bringup.ts +3 -2
- package/src/onboard/steps/configfill.ts +23 -7
- package/src/onboard/steps/loginshare.ts +258 -0
- package/src/onboard/steps/memory.ts +18 -2
- package/src/onboard/steps/repoconfig.ts +85 -0
- package/src/onboard/toolnames.ts +2 -2
- package/src/piagentdir.ts +12 -0
- package/src/pilogin.ts +32 -16
- package/src/qcrbin.ts +16 -11
- package/src/reviewargs.ts +119 -0
- package/src/reviewcli.ts +6 -167
- package/src/reviewdetach.ts +107 -13
- package/src/scancli.ts +75 -826
- package/src/selfimprove.ts +90 -0
- package/src/session-command-supervisor.ts +41 -8
- 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 +126 -11
- package/src/takeover.ts +5 -0
- package/src/takeoverpi.ts +3 -2
- package/src/tui/alerts.ts +9 -2
- package/src/tui/board.ts +2 -2
- package/src/tui/companion.ts +2 -1
- package/src/tui/deploytab.ts +2 -2
- package/src/tui/index.ts +9 -6
- package/src/tui/memory.ts +3 -15
- package/src/tui/memoryurl.ts +22 -0
- package/src/tui/remote.ts +1 -1
- package/src/tui/route.ts +2 -2
- package/src/tui/setupstate.ts +20 -16
- package/src/tui/sshtriage.ts +1 -1
- package/src/verdicts.ts +13 -0
- package/src/wire.ts +9 -4
- package/src/workerbreaker.ts +41 -5
- package/src/workerworld.ts +9 -1
- 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 -75
- 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 -103
- package/src/setup/ledger.ts +0 -324
- package/src/setup/stage2.ts +0 -926
- package/src/setup/textsniff.ts +0 -95
package/extensions/setup-scan.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { usd, findingLocation } from "../src/setup/presentation.js";
|
|
2
1
|
// setup-scan — the scan half of the companion's setup tool belt. Not an
|
|
3
2
|
// extension of its own: extensions/setup.ts (the one setup & scan extension)
|
|
4
3
|
// registers these beside the setup_* tools. The companion judges
|
|
@@ -34,8 +33,6 @@ import { usd, findingLocation } from "../src/setup/presentation.js";
|
|
|
34
33
|
//
|
|
35
34
|
// Convention (setup.ts, onboarding.ts): every tool answers a refusal as its
|
|
36
35
|
// result, never a throw.
|
|
37
|
-
import { FINDING_STATES } from "../src/setup/findings.js";
|
|
38
|
-
import { join } from "node:path";
|
|
39
36
|
import {
|
|
40
37
|
textResult as text,
|
|
41
38
|
refusedResult as refused,
|
|
@@ -46,708 +43,69 @@ import { homeProfileLookup, stateHome } from "../src/install.js";
|
|
|
46
43
|
import { scanCasesForEnv } from "@quickchatai/pi-quickreview/src/engine/auth.js";
|
|
47
44
|
import { piCaseRunner } from "../src/setup/caserunner.js";
|
|
48
45
|
import type { CaseRunner, SetupEngine } from "../src/setup/engine.js";
|
|
49
|
-
import type {
|
|
50
|
-
FindingsFilter,
|
|
51
|
-
FindingState,
|
|
52
|
-
MatchQuestionView,
|
|
53
|
-
ScanFinding,
|
|
54
|
-
ValidityView,
|
|
55
|
-
} from "../src/setup/findings.js";
|
|
56
|
-
import {
|
|
57
|
-
VALIDITY_ORDER,
|
|
58
|
-
clickStopScan,
|
|
59
|
-
readStopResult,
|
|
60
|
-
scanStopOutcome,
|
|
61
|
-
validityOf,
|
|
62
|
-
type ScanStopRequest,
|
|
63
|
-
} from "../src/setup/scanreport.js";
|
|
64
|
-
import { FindingsStore, dismissedLookalike, lookalikeRank } from "../src/setup/findings.js";
|
|
46
|
+
import type { FindingsStore } from "../src/setup/findings.js";
|
|
65
47
|
import { stateHomeFs } from "../src/setup/adapters.js";
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
priceIsMeasured,
|
|
70
|
-
priceLabel,
|
|
71
|
-
type StoredStage2Proposal,
|
|
72
|
-
} from "../src/setup/stage2request.js";
|
|
48
|
+
import { loadScanConfig } from "../src/setup/scanbuild.js";
|
|
49
|
+
import { assembleScan, scanRuns } from "@quickchatai/pi-quickreview/src/scan/scanassemble.js";
|
|
73
50
|
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
51
|
+
FINDINGS_LIMIT,
|
|
52
|
+
FINDINGS_LIMIT_MAX,
|
|
53
|
+
answerMatch,
|
|
54
|
+
findingsFilter,
|
|
55
|
+
listFindings,
|
|
56
|
+
previewDetails,
|
|
57
|
+
proposalDetails,
|
|
58
|
+
renderFindings,
|
|
59
|
+
renderPreview,
|
|
60
|
+
renderProposal,
|
|
61
|
+
renderQuestion,
|
|
62
|
+
renderStatus,
|
|
63
|
+
renderSuggestions,
|
|
64
|
+
stage2StartedLine,
|
|
65
|
+
startedDetails,
|
|
66
|
+
startedLine,
|
|
67
|
+
stopScan,
|
|
68
|
+
type StopChannel,
|
|
69
|
+
} from "@quickchatai/pi-quickreview/src/scan/scanrender.js";
|
|
70
|
+
import type { ScanRunner, ScanSettings } from "../src/setup/scanrun.js";
|
|
90
71
|
import { ScanTriage, type CloseFixedResult } from "../src/setup/triage.js";
|
|
91
72
|
import type { MemoryStore } from "../src/memory/store.js";
|
|
92
73
|
import type { TrackerSink } from "../src/setup/engine.js";
|
|
93
|
-
import type {
|
|
94
|
-
import { fileLease, type ScanLease } from "../src/setup/scanlease.js";
|
|
74
|
+
import type { ScanLease } from "../src/setup/scanlease.js";
|
|
95
75
|
|
|
96
76
|
const NO_PARAMS = { type: "object", properties: {} } as const;
|
|
97
77
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
export const CLI_VERBS: ScanVerbs = {
|
|
130
|
-
start: (p) =>
|
|
131
|
-
`qcr scan start --key ${p.key} --sha ${p.sha}${p.mode === "delta" ? " --delta" : ""}`,
|
|
132
|
-
matches: "qcr scan matches",
|
|
133
|
-
match: (key) => `qcr scan match --key ${key} OLD=NEW… — the pairs`,
|
|
134
|
-
stage2Start: (key) =>
|
|
135
|
-
`qcr scan stage2 start --key ${key} — only after the operator agreed to the cost; the TUI's start click starts the same proposal. ` +
|
|
136
|
-
"To run only some rows: add --rows with their ids (e.g. --rows row-1,row-3) — only runnable rows, and the run stops hard at their total.",
|
|
137
|
-
status: "qcr scan status",
|
|
138
|
-
stop: "qcr scan stop",
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/** A delta's unchanged-but-owed files: those no reading covers, and — on a
|
|
142
|
-
* deep delta — those a standard reading covers but no deep one does yet. */
|
|
143
|
-
function owedClause(d: ScanPreview["delta"]): string {
|
|
144
|
-
if (d === null || d.owed <= 0) return "";
|
|
145
|
-
const shallow = Math.min(d.owed_shallow ?? 0, d.owed);
|
|
146
|
-
const unread = d.owed - shallow;
|
|
147
|
-
const parts: string[] = [];
|
|
148
|
-
if (unread > 0)
|
|
149
|
-
parts.push(
|
|
150
|
-
`${unread} of the files are unchanged but no reading covers them yet (never read, cut short or unreadable before)`,
|
|
151
|
-
);
|
|
152
|
-
if (shallow > 0)
|
|
153
|
-
parts.push(`${shallow} of the files are unchanged but not yet read at deep depth`);
|
|
154
|
-
return `; ${parts.join("; ")} — read first`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function renderPreview(p: ScanPreview, verbs: ScanVerbs = TOOL_VERBS): string {
|
|
158
|
-
const out: string[] = [];
|
|
159
|
-
const deep = p.depth === "deep";
|
|
160
|
-
const head = `${deep ? "(deep) " : ""}at ${p.sha.slice(0, 12)} — ${p.settings.tier} at ${p.settings.effort} effort, skeptic ${p.settings.validator ? `on (at the lens's tier, ${p.settings.validate_tier}; its cost is in the price)` : "off (findings stay unjudged — validity unknown)"}`;
|
|
161
|
-
// A rate measured at the other depth stands in until this depth is priced.
|
|
162
|
-
const across = p.rate_across_depth
|
|
163
|
-
? ` — from readings at the other depth: nothing at ${p.depth} depth is priced at these settings yet`
|
|
164
|
-
: "";
|
|
165
|
-
if (p.mode === "delta") {
|
|
166
|
-
out.push(
|
|
167
|
-
`DELTA re-scan ${head}, against the last scan at ${p.delta?.since.slice(0, 12) ?? "?"}.`,
|
|
168
|
-
`Files whose content changed since, and any not yet read whole: ${p.files} files, ${p.lines} lines in ${p.cases.length} case(s) — ` +
|
|
169
|
-
`${p.covered} already read at this content` +
|
|
170
|
-
owedClause(p.delta) +
|
|
171
|
-
`. Estimate ${usd(p.estimate_usd)}, measured at ` +
|
|
172
|
-
`${usd(p.rate?.usd_per_kline ?? null)}/kline over ${p.rate?.cases ?? 0} finished case(s)${across}. The run stops hard at the estimate. ` +
|
|
173
|
-
"No deep pass runs on a delta.",
|
|
174
|
-
);
|
|
175
|
-
} else if (p.mode === "calibration") {
|
|
176
|
-
out.push(
|
|
177
|
-
`CALIBRATION run ${head}.`,
|
|
178
|
-
`No measured price for stage 1 at these settings yet (${p.calibration.measured} of ${p.calibration.wanted} priced cases). ` +
|
|
179
|
-
`This run reads ${p.cases.length} case(s) from units spread across the size range and measures what they cost; ` +
|
|
180
|
-
`it stops hard at ${usd(p.cap_usd)}. Its findings are real stage-1 findings. Preview again afterwards for the full estimate` +
|
|
181
|
-
(p.remaining_cases > 0 ? ` (${p.remaining_cases} more case(s) after it).` : "."),
|
|
182
|
-
);
|
|
183
|
-
} else {
|
|
184
|
-
out.push(
|
|
185
|
-
`STAGE 1 ${head}.`,
|
|
186
|
-
`Estimate ${usd(p.estimate_usd)} for ${p.cases.length} case(s), ${p.files} files, ${p.lines} lines — measured at ` +
|
|
187
|
-
`${usd(p.rate?.usd_per_kline ?? null)}/kline over ${p.rate?.cases ?? 0} finished case(s)${across}. The run stops hard at the estimate.`,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
if (deep) {
|
|
191
|
-
out.push(
|
|
192
|
-
"DEEP: pass-scan names a deep lens for every focus file, and a file the ledger holds only a standard reading of is read again. " +
|
|
193
|
-
"A deep stage 2 proposal then adds the floor — the [scan] deep_lenses over every file this stage reads.",
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
const rows = p.cases
|
|
197
|
-
.slice(0, 40)
|
|
198
|
-
.map(
|
|
199
|
-
(c) =>
|
|
200
|
-
` - ${c.id}: ${c.files.length} files, ${c.lines} lines${c.estimate_usd !== null ? `, ~${usd(c.estimate_usd)}` : ""}`,
|
|
201
|
-
);
|
|
202
|
-
if (p.cases.length > 40) rows.push(` … and ${p.cases.length - 40} more`);
|
|
203
|
-
out.push(rows.join("\n"));
|
|
204
|
-
const skipped = Object.entries(p.excluded)
|
|
205
|
-
.filter(([, n]) => n > 0)
|
|
206
|
-
.map(([why, n]) => `${n} ${why}`);
|
|
207
|
-
out.push(
|
|
208
|
-
`Not read: ${skipped.length ? skipped.join(", ") : "nothing excluded"}; ${p.covered} already in the ledger at this content; ` +
|
|
209
|
-
`${p.unreadable} unreadable${p.settings.exclude.length ? `; exclude globs ${p.settings.exclude.join(", ")}` : ""}.`,
|
|
210
|
-
);
|
|
211
|
-
// What the tree lists but no lens can read, by reason and by name — a
|
|
212
|
-
// binary is REPORTED as skipped, never silently absent from coverage.
|
|
213
|
-
const skippedBy = new Map<string, string[]>();
|
|
214
|
-
for (const f of p.skipped ?? []) skippedBy.set(f.why, [...(skippedBy.get(f.why) ?? []), f.path]);
|
|
215
|
-
for (const [why, paths] of skippedBy) {
|
|
216
|
-
const label =
|
|
217
|
-
why === "binary" ? "binary" : why === "too-big" ? "over the size cap" : "unreadable";
|
|
218
|
-
out.push(
|
|
219
|
-
`skipped: ${label} — ${paths.length} file(s): ${paths.slice(0, 5).map(escapeControls).join(", ")}${paths.length > 5 ? ", …" : ""}; not read by any lens.`,
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
if (p.unassigned.length > 0) {
|
|
223
|
-
out.push(
|
|
224
|
-
`! ${p.unassigned.length} file(s) no unit owns at this sha (e.g. ${p.unassigned.slice(0, 3).join(", ")}) — the unit cut may need adjusting; they are not read.`,
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
if (p.reconcile_only) {
|
|
228
|
-
out.push(
|
|
229
|
-
`Nothing to read, but ${p.reconcile_only}: the run reads no case and only reconciles the findings (no cost).`,
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
for (const b of p.blockers) out.push(`✗ ${b}`);
|
|
233
|
-
if (p.blockers.length === 0) {
|
|
234
|
-
out.push(`To run it: ${verbs.start(p)} — only after the operator agreed to the cost.`);
|
|
235
|
-
}
|
|
236
|
-
return out.join("\n\n");
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/** A run's findings by validity tier — `(2 confirmed, 1 likely, 3 doubtful)`
|
|
240
|
-
* — from the per-case counts; `unknown` for what no skeptic judged (the
|
|
241
|
-
* validator off or failed, or a case recorded before the tiers). */
|
|
242
|
-
function tierCounts(r: NonNullable<ScanStatus["run"]>, findings: number, doubted: number): string {
|
|
243
|
-
if (!r.settings.validator)
|
|
244
|
-
return doubted ? ` (${doubted} may not be valid)` : " (skeptic off — validity unknown)";
|
|
245
|
-
const confirmed = r.cases.reduce((n, c) => n + (c.confirmed ?? 0), 0);
|
|
246
|
-
const likely = r.cases.reduce((n, c) => n + (c.likely ?? 0), 0);
|
|
247
|
-
const unknown = findings - confirmed - likely - doubted;
|
|
248
|
-
if (findings === 0) return "";
|
|
249
|
-
return ` (${confirmed} confirmed, ${likely} likely, ${doubted} doubtful${unknown > 0 ? `, ${unknown} unknown` : ""})`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function renderStatus(s: ScanStatus, verbs: ScanVerbs = TOOL_VERBS): string {
|
|
253
|
-
const out: string[] = [];
|
|
254
|
-
for (const p of s.problems) out.push(`✗ ${p}`);
|
|
255
|
-
const r = s.run;
|
|
256
|
-
if (r === null) {
|
|
257
|
-
out.push(s.problems.length > 0 ? "Latest scan: UNKNOWN — see above." : "No scan has run yet.");
|
|
258
|
-
} else {
|
|
259
|
-
const done = r.cases.filter((c) => c.status === "done").length;
|
|
260
|
-
const findings = r.cases.reduce((n, c) => n + c.findings, 0);
|
|
261
|
-
const doubted = r.cases.reduce((n, c) => n + c.doubted, 0);
|
|
262
|
-
out.push(
|
|
263
|
-
`${r.id} (${r.mode === "stage2" ? "stage 2" : r.mode}${r.settings.depth === "deep" ? ", deep" : ""}) at ${r.sha.slice(0, 12)}: ${r.display.toUpperCase()} — ${done}/${r.cases.length} cases, ` +
|
|
264
|
-
`${findings} finding(s)${tierCounts(r, findings, doubted)}, spent ${usd(r.spent_usd)} of ${usd(r.cap_usd)}` +
|
|
265
|
-
(r.unpriced_cases
|
|
266
|
-
? ` (${r.unpriced_cases} unpriced case(s) counted at their estimate)`
|
|
267
|
-
: ""),
|
|
268
|
-
);
|
|
269
|
-
// Stage 2 is re-entered by a new proposal (the rows already read are
|
|
270
|
-
// covered, free); stage 1 by a new preview.
|
|
271
|
-
const again = r.mode === "stage2" ? "Propose stage 2 again" : "Preview";
|
|
272
|
-
if (r.display === "interrupted")
|
|
273
|
-
out.push(
|
|
274
|
-
`It was interrupted (the process driving it went away — the companion restarted, or a \`qcr scan\` driver died). ${again} and start again — the ledger resumes it.`,
|
|
275
|
-
);
|
|
276
|
-
if (r.display === "capped")
|
|
277
|
-
out.push(`It stopped at its hard cap. ${again} for the price of what is left.`);
|
|
278
|
-
if (r.mode === "stage2") {
|
|
279
|
-
for (const c of r.cases.slice(0, 40)) {
|
|
280
|
-
out.push(
|
|
281
|
-
` - ${c.id} ${c.lens ?? "?"} at ${c.tier ?? "?"} (${(c.units ?? [c.unit]).join(", ")}): ${c.status}, ${c.findings} finding(s), ${usd(c.cost_usd)}` +
|
|
282
|
-
(c.reads !== undefined ? `, ${c.reads} read(s)` : "") +
|
|
283
|
-
(c.unopened?.length ? `, ${c.unopened.length} focus file(s) not opened` : ""),
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
// Where to read back what a lens did, when the runner kept it.
|
|
288
|
-
if (r.cases.some((c) => c.transcript !== undefined))
|
|
289
|
-
out.push(
|
|
290
|
-
`Transcripts: ${SCAN_TRANSCRIPTS_DIR}/${r.id}/ under the state home (one .jsonl per case, .validate.jsonl for its skeptic).`,
|
|
291
|
-
);
|
|
292
|
-
const failed = r.cases.filter((c) => c.status === "failed");
|
|
293
|
-
for (const c of failed.slice(0, 10)) out.push(` ✗ ${c.id}: ${c.error}`);
|
|
294
|
-
for (const p of r.problems.slice(-5)) out.push(` ! ${p}`);
|
|
295
|
-
if (r.tickets_closed)
|
|
296
|
-
out.push(`Tickets closed as fixed at ${r.sha.slice(0, 12)}: ${r.tickets_closed}.`);
|
|
297
|
-
if (r.matching && (r.matching.fixed > 0 || r.matching.pending > 0)) {
|
|
298
|
-
out.push(
|
|
299
|
-
`Findings no longer reported: ${r.matching.fixed} fixed` +
|
|
300
|
-
(r.matching.pending > 0
|
|
301
|
-
? `, ${r.matching.pending} waiting on your match (${verbs.matches})`
|
|
302
|
-
: ""),
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
// A dismissal is sticky: what would have been fixed is only stamped.
|
|
306
|
-
const dismissedGone = r.matching?.dismissed_unreported ?? 0;
|
|
307
|
-
if (dismissedGone > 0) {
|
|
308
|
-
out.push(
|
|
309
|
-
`Dismissed and no longer reported: ${dismissedGone} — kept dismissed (a scan never overwrites the operator's triage), stamped for audit.`,
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
// A record written before the stamp existed has no count: nothing to say.
|
|
313
|
-
const notReproduced = r.matching?.not_reproduced ?? 0;
|
|
314
|
-
if (notReproduced > 0) {
|
|
315
|
-
out.push(
|
|
316
|
-
`Not reproduced: ${notReproduced} open finding(s) re-read at UNCHANGED content without being reported again — ` +
|
|
317
|
-
"kept open (only changed code is fixed); dismiss any that were noise.",
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (s.ledger) {
|
|
322
|
-
out.push(
|
|
323
|
-
`Ledger: ${s.ledger.files} file(s) read by stage 1` +
|
|
324
|
-
(s.ledger.deep_files ? `, ${s.ledger.deep_files} file × lens read by stage 2` : "") +
|
|
325
|
-
`, ${usd(s.ledger.cost_usd)} recorded.`,
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
return out.join("\n");
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/** Findings in validity order — confirmed, likely, doubtful, unknown — the
|
|
332
|
-
* store's order (most severe first) kept inside each tier, a finding that
|
|
333
|
-
* looks like a dismissed one below the rest of its tier. */
|
|
334
|
-
export function byValidity(fs: readonly ScanFinding[]): ScanFinding[] {
|
|
335
|
-
return VALIDITY_ORDER.flatMap((v) => {
|
|
336
|
-
const tier = fs.filter((f) => validityOf(f) === v);
|
|
337
|
-
return [
|
|
338
|
-
...tier.filter((f) => lookalikeRank(f) === 0),
|
|
339
|
-
...tier.filter((f) => lookalikeRank(f) === 1),
|
|
340
|
-
];
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/** A new finding that resembles one the operator dismissed: which, and why
|
|
345
|
-
* — flagged for triage, never dismissed by the scan. */
|
|
346
|
-
function lookalikeNote(f: ScanFinding): string {
|
|
347
|
-
const d = dismissedLookalike(f);
|
|
348
|
-
return d
|
|
349
|
-
? `, LOOKS LIKE DISMISSED ${d.fingerprint.slice(0, 12)}: ${d.reason} — not auto-dismissed`
|
|
350
|
-
: "";
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function validityNote(f: ScanFinding): string {
|
|
354
|
-
const v = validityOf(f);
|
|
355
|
-
if (v === "doubtful")
|
|
356
|
-
return `, DOUBTFUL — ${f.doubt?.word ?? "?"}${f.doubt?.class ? ` [${f.doubt.class}]` : ""}`;
|
|
357
|
-
if (v === "unknown")
|
|
358
|
-
return f.doubt
|
|
359
|
-
? `, MAY NOT BE VALID — ${f.doubt.word}`
|
|
360
|
-
: f.validated
|
|
361
|
-
? ", validity unknown"
|
|
362
|
-
: ", unvalidated";
|
|
363
|
-
return `, ${v.toUpperCase()}`;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export function renderFindings(fs: readonly ScanFinding[], total: number): string {
|
|
367
|
-
if (fs.length === 0) return "No stored scan findings match.";
|
|
368
|
-
const rows = fs.map(
|
|
369
|
-
(f) =>
|
|
370
|
-
`- [${f.severity}] ${findingLocation(f)} (${f.unit}, ${f.kind}, ${f.state}` +
|
|
371
|
-
validityNote(f) +
|
|
372
|
-
`${f.capped_from ? `, ${f.capped_from}→${f.severity}: ${f.reachability}` : ""}` +
|
|
373
|
-
`${f.anchor_quoted === false ? ", ANCHOR NOT QUOTED — the lines may not be the defect's" : ""}` +
|
|
374
|
-
`${f.fixed ? ` at ${f.fixed.sha.slice(0, 12)}, was ${f.fixed.from}` : ""}` +
|
|
375
|
-
`${f.state === "dismissed" && f.unreported ? `, no longer reported at ${f.unreported.sha.slice(0, 12)} — kept dismissed` : ""}` +
|
|
376
|
-
`${f.not_reproduced ? `, NOT REPRODUCED by ${f.not_reproduced.count} re-read(s) of unchanged code, latest ${f.not_reproduced.run}` : ""}` +
|
|
377
|
-
`${lookalikeNote(f)}) ` +
|
|
378
|
-
`${f.claim} [${f.fingerprint.slice(0, 12)}]`,
|
|
379
|
-
);
|
|
380
|
-
if (total > fs.length) rows.push(`… ${total - fs.length} more — narrow the filter.`);
|
|
381
|
-
return rows.join("\n");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
export function renderQuestion(q: MatchQuestionView | null, verbs: ScanVerbs = TOOL_VERBS): string {
|
|
385
|
-
if (q === null)
|
|
386
|
-
return "No match question is pending: every finding a re-scan stopped reporting has been settled.";
|
|
387
|
-
const row = (f: ScanFinding): string =>
|
|
388
|
-
` - ${f.fingerprint} [${f.severity}] ${findingLocation(f)} (${f.unit}, ${f.lens}, ${f.kind}` +
|
|
389
|
-
`${f.state === "new" ? "" : `, ${f.state}`}${lookalikeNote(f)}) ${f.claim}`;
|
|
390
|
-
return [
|
|
391
|
-
`MATCH QUESTION ${q.key} (re-scan ${q.run} at ${q.sha.slice(0, 12)}).`,
|
|
392
|
-
`No longer reported exactly (${q.unreported.length}):`,
|
|
393
|
-
...q.unreported.map(row),
|
|
394
|
-
`Fresh — first seen by the re-scan, possibly one of the above moved or reworded (${q.fresh.length}):`,
|
|
395
|
-
...q.fresh.map(row),
|
|
396
|
-
`Answer with ${verbs.match(q.key)} that are the SAME finding. Every unpaired old one becomes fixed (a dismissed one stays dismissed); every unpaired fresh one stays new.`,
|
|
397
|
-
].join("\n");
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
export function renderSuggestions(
|
|
401
|
-
sugs: readonly StoredSuggestion[],
|
|
402
|
-
settings: ScanSettings | null,
|
|
403
|
-
): string {
|
|
404
|
-
const out: string[] = [];
|
|
405
|
-
if (sugs.length === 0)
|
|
406
|
-
out.push("No deep-lens suggestions stored — stage 1 has not suggested any yet.");
|
|
407
|
-
else {
|
|
408
|
-
const byLens = new Map<string, StoredSuggestion[]>();
|
|
409
|
-
for (const s of sugs) byLens.set(s.lens, [...(byLens.get(s.lens) ?? []), s]);
|
|
410
|
-
for (const [lens, ss] of [...byLens.entries()].sort(
|
|
411
|
-
(a, b) => b[1].length - a[1].length || (a[0] < b[0] ? -1 : 1),
|
|
412
|
-
)) {
|
|
413
|
-
out.push(`${lens} (${ss.length}):`);
|
|
414
|
-
for (const s of ss.slice(0, 30))
|
|
415
|
-
out.push(` - [${s.unit}] ${s.files.join(", ")} — ${s.reason} (at ${s.sha.slice(0, 12)})`);
|
|
416
|
-
if (ss.length > 30) out.push(` … and ${ss.length - 30} more`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (settings) {
|
|
420
|
-
const lenses = stage2Lenses(settings.lenses);
|
|
421
|
-
out.push(
|
|
422
|
-
"",
|
|
423
|
-
`Stage 2 may run: ${lenses.map((l) => `${l} (${lensTiers(settings.lenses, l, settings.riskPasses ?? []).join("|")})`).join(", ")}.`,
|
|
424
|
-
`One row reads at most ${settings.scan.case_max_files} files / ${settings.scan.case_max_lines} lines, paired tests included; at most ${MAX_STAGE2_ROWS} rows.`,
|
|
425
|
-
settings.scan.depth === "deep"
|
|
426
|
-
? `Depth: deep — a proposal adds the floor after your rows (${settings.scan.deep_lenses.filter((l) => lenses.includes(l)).join(", ") || "no lens: every deep_lenses lens is switched off"} over every file stage 1 reads; not counted in the ${MAX_STAGE2_ROWS}), so your rows may be none. deep: false proposes a standard plan.`
|
|
427
|
-
: "Depth: standard — deep: true on a proposal makes it deep (the floor, every lens told to dig).",
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
return out.join("\n");
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/** The floor's rows, one line per lens: its row ids, files, cost and what
|
|
434
|
-
* the budget left out — the floor is bounded by the tree, so its rows are
|
|
435
|
-
* summed, never listed one by one. */
|
|
436
|
-
function renderFloor(p: StoredStage2Proposal): string[] {
|
|
437
|
-
const floor = p.rows.filter((r) => r.floor);
|
|
438
|
-
const out: string[] = [];
|
|
439
|
-
const lenses = [...new Set(floor.map((r) => r.lens))];
|
|
440
|
-
for (const lens of lenses) {
|
|
441
|
-
const rows = floor.filter((r) => r.lens === lens);
|
|
442
|
-
const run = rows.filter((r) => r.status === "run");
|
|
443
|
-
const over = rows.filter((r) => r.status === "over-budget");
|
|
444
|
-
const covered = rows.filter((r) => r.status === "covered");
|
|
445
|
-
const ids = rows.length === 1 ? rows[0]!.id : `${rows[0]!.id}…${rows[rows.length - 1]!.id}`;
|
|
446
|
-
out.push(
|
|
447
|
-
` ${lens} (floor ${ids}) at ${rows[0]!.tier}: ${rows.length} row(s) over ${[...new Set(rows.flatMap((r) => r.units))].length} unit(s), ` +
|
|
448
|
-
`${rows.reduce((n, r) => n + r.files.length, 0)} file(s), ${rows.reduce((n, r) => n + r.lines, 0)} lines — ` +
|
|
449
|
-
`${run.length} to run, ~${usd(run.reduce((n, r) => n + r.estimate_usd, 0))} (${priceLabel(rows[0]!.price)})` +
|
|
450
|
-
(covered.length ? `; ${covered.length} covered (free)` : "") +
|
|
451
|
-
(over.length
|
|
452
|
-
? `; OVER BUDGET, left out: ${over.map((r) => r.id).join(", ")} (${usd(over.reduce((n, r) => n + r.estimate_usd, 0))})`
|
|
453
|
-
: ""),
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
const f = p.floor;
|
|
457
|
-
if (f) {
|
|
458
|
-
if (f.lenses.length === 0)
|
|
459
|
-
out.push(" The floor has no lens: every [scan] deep_lenses lens is switched off.");
|
|
460
|
-
if (f.skipped.length > 0)
|
|
461
|
-
out.push(` Switched off by the profile, no floor rows: ${f.skipped.join(", ")}.`);
|
|
462
|
-
if (f.covered > 0 || f.named > 0)
|
|
463
|
-
out.push(
|
|
464
|
-
` Left out of the floor: ${f.covered} file × lens already read deep at this content` +
|
|
465
|
-
(f.named > 0 ? `, ${f.named} named by your rows for the same lens` : "") +
|
|
466
|
-
".",
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
return out;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
export function renderProposal(p: StoredStage2Proposal, verbs: ScanVerbs = TOOL_VERBS): string {
|
|
473
|
-
const out: string[] = [];
|
|
474
|
-
const run = p.rows.filter((r) => r.status === "run");
|
|
475
|
-
const deep = p.depth === "deep";
|
|
476
|
-
out.push(
|
|
477
|
-
`STAGE 2 proposal ${deep ? "(deep) " : ""}at ${p.sha.slice(0, 12)} — ${run.length} row(s) to run, estimate ${usd(p.cap_usd)} (the run stops hard there)` +
|
|
478
|
-
(p.budget_usd !== null ? `, fitted to a ${usd(p.budget_usd)} budget` : "") +
|
|
479
|
-
`; validator ${p.validator ? "on" : "off"}.`,
|
|
480
|
-
);
|
|
481
|
-
if (deep) {
|
|
482
|
-
const own = run.filter((r) => !r.floor);
|
|
483
|
-
const floor = run.filter((r) => r.floor);
|
|
484
|
-
const sum = (rs: readonly { estimate_usd: number }[]): number =>
|
|
485
|
-
rs.reduce((n, r) => n + r.estimate_usd, 0);
|
|
486
|
-
out.push(
|
|
487
|
-
`DEEP: your rows ${own.length} to run, ~${usd(sum(own))}; the floor ${floor.length} to run, ~${usd(sum(floor))}. ` +
|
|
488
|
-
"Every lens is told to dig past its row's files; a standard reading covers nothing here, and the floor runs after your rows (the budget fits them first).",
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
const rows = p.rows
|
|
492
|
-
.filter((r) => !r.floor)
|
|
493
|
-
.map((r) => {
|
|
494
|
-
const mark =
|
|
495
|
-
r.status === "run"
|
|
496
|
-
? ""
|
|
497
|
-
: r.status === "covered"
|
|
498
|
-
? " — COVERED (already read at this content, free)"
|
|
499
|
-
: ` — OVER BUDGET (${usd(r.estimate_usd)}, left out)`;
|
|
500
|
-
const price = priceLabel(r.price);
|
|
501
|
-
return [
|
|
502
|
-
` ${r.id}: ${r.lens} at ${r.tier} over ${r.units.join(" + ")}${r.units.length > 1 ? " (cross-module)" : ""} — ${r.files.length} file(s)` +
|
|
503
|
-
(r.tests.length ? ` + ${r.tests.length} paired test(s)` : "") +
|
|
504
|
-
`, ${r.lines} lines, ~${usd(r.estimate_usd)} (${price})${mark}`,
|
|
505
|
-
` ${r.reason}`,
|
|
506
|
-
` files: ${r.files.map((f) => f.path).join(", ")}`,
|
|
507
|
-
...(r.tests.length ? [` tests: ${r.tests.map((f) => f.path).join(", ")}`] : []),
|
|
508
|
-
...(r.unpaired.length ? [` not paired (row full): ${r.unpaired.join(", ")}`] : []),
|
|
509
|
-
].join("\n");
|
|
510
|
-
});
|
|
511
|
-
if (rows.length > 0) out.push(rows.join("\n"));
|
|
512
|
-
if (deep) out.push(["The deep floor:", ...renderFloor(p)].join("\n"));
|
|
513
|
-
const priced = p.rows.filter((r) => r.status !== "covered");
|
|
514
|
-
const measured = priced.filter((r) => priceIsMeasured(r.price)).length;
|
|
515
|
-
if (priced.length > 0) {
|
|
516
|
-
out.push(
|
|
517
|
-
`Priced by measurement: ${measured} row(s); by estimate: ${priced.length - measured} row(s).` +
|
|
518
|
-
(measured < priced.length
|
|
519
|
-
? " An estimate stands for this run once approved; the next proposal is priced from what the run measures."
|
|
520
|
-
: ""),
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
if (p.over_budget_usd > 0) out.push(`Left out by the budget: ${usd(p.over_budget_usd)} of rows.`);
|
|
524
|
-
if (p.runnable > 0) {
|
|
525
|
-
out.push(`To run it: ${verbs.stage2Start(p.key)}`);
|
|
526
|
-
} else out.push("✗ nothing to run: every row is covered or over the budget.");
|
|
527
|
-
return out.join("\n\n");
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// ---- shared verbs ------------------------------------------------------------
|
|
531
|
-
//
|
|
532
|
-
// What a tool and its `qcr scan` subcommand (src/scancli.ts) both answer:
|
|
533
|
-
// the checks, the orders and the structured results. Each surface renders
|
|
534
|
-
// them its own way; neither carries a rule of its own.
|
|
535
|
-
|
|
536
|
-
/** A preview's structured result: its cases summarised (counts, not files). */
|
|
537
|
-
export function previewDetails(p: ScanPreview): Record<string, unknown> {
|
|
538
|
-
return {
|
|
539
|
-
...p,
|
|
540
|
-
cases: p.cases.map((c) => ({
|
|
541
|
-
id: c.id,
|
|
542
|
-
files: c.files.length,
|
|
543
|
-
lines: c.lines,
|
|
544
|
-
estimate_usd: c.estimate_usd,
|
|
545
|
-
})),
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/** A started run, as a start answers it. */
|
|
550
|
-
export function startedDetails(run: ScanRun): {
|
|
551
|
-
id: string;
|
|
552
|
-
mode: ScanRun["mode"];
|
|
553
|
-
sha: string;
|
|
554
|
-
cases: number;
|
|
555
|
-
cap_usd: number;
|
|
556
|
-
} {
|
|
557
|
-
return {
|
|
558
|
-
id: run.id,
|
|
559
|
-
mode: run.mode,
|
|
560
|
-
sha: run.sha,
|
|
561
|
-
cases: run.cases.length,
|
|
562
|
-
cap_usd: run.cap_usd,
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
export { FINDING_STATES } from "../src/setup/findings.js";
|
|
567
|
-
/** How many findings a listing shows unless told otherwise, and at most. */
|
|
568
|
-
export const FINDINGS_LIMIT = 50;
|
|
569
|
-
export const FINDINGS_LIMIT_MAX = 200;
|
|
570
|
-
|
|
571
|
-
/** A findings filter from loose parameters (a tool call's, the CLI's flags),
|
|
572
|
-
* or what is wrong with one. */
|
|
573
|
-
export function findingsFilter(
|
|
574
|
-
p: Record<string, unknown>,
|
|
575
|
-
): { ok: true; filter: FindingsFilter } | { ok: false; error: string } {
|
|
576
|
-
const filter: FindingsFilter = {};
|
|
577
|
-
if (typeof p.state === "string") {
|
|
578
|
-
if (!(FINDING_STATES as readonly string[]).includes(p.state))
|
|
579
|
-
return { ok: false, error: `Unknown state ${JSON.stringify(p.state)}.` };
|
|
580
|
-
filter.state = p.state as FindingState;
|
|
581
|
-
}
|
|
582
|
-
if (typeof p.severity === "string") {
|
|
583
|
-
if (!(SEVERITIES as readonly string[]).includes(p.severity))
|
|
584
|
-
return { ok: false, error: `Unknown severity ${JSON.stringify(p.severity)}.` };
|
|
585
|
-
filter.severity = p.severity as ScanSeverity;
|
|
586
|
-
}
|
|
587
|
-
if (typeof p.lens === "string") filter.lens = p.lens;
|
|
588
|
-
if (typeof p.unit === "string") filter.unit = p.unit;
|
|
589
|
-
if (typeof p.doubted === "boolean") filter.doubted = p.doubted;
|
|
590
|
-
if (typeof p.validity === "string") {
|
|
591
|
-
if (!(VALIDITY_ORDER as readonly string[]).includes(p.validity))
|
|
592
|
-
return {
|
|
593
|
-
ok: false,
|
|
594
|
-
error: `Unknown validity ${JSON.stringify(p.validity)} — confirmed, likely, doubtful or unknown.`,
|
|
595
|
-
};
|
|
596
|
-
filter.validity = p.validity as ValidityView;
|
|
597
|
-
}
|
|
598
|
-
return { ok: true, filter };
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/** The stored findings a filter keeps, in validity order (`byValidity`),
|
|
602
|
-
* with the count in each tier — or why the store cannot be read. */
|
|
603
|
-
export function listFindings(
|
|
604
|
-
findings: FindingsStore,
|
|
605
|
-
filter: FindingsFilter,
|
|
606
|
-
):
|
|
607
|
-
| { ok: true; findings: ScanFinding[]; validity: Record<ValidityView, number> }
|
|
608
|
-
| { ok: false; error: string } {
|
|
609
|
-
const { findings: listed, error } = findings.list(filter);
|
|
610
|
-
if (error !== null) return { ok: false, error };
|
|
611
|
-
const fs = byValidity(listed);
|
|
612
|
-
const validity = Object.fromEntries(
|
|
613
|
-
VALIDITY_ORDER.map((v) => [v, fs.filter((f) => validityOf(f) === v).length]),
|
|
614
|
-
) as Record<ValidityView, number>;
|
|
615
|
-
return { ok: true, findings: fs, validity };
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/** Answer the match question whole, then close the tickets of the promoted
|
|
619
|
-
* findings it fixed — and the lines that say what was settled. */
|
|
620
|
-
export async function answerMatch(
|
|
621
|
-
findings: FindingsStore,
|
|
622
|
-
args: { key?: unknown; pairs?: unknown },
|
|
623
|
-
afterMatch: (() => Promise<CloseFixedResult>) | null,
|
|
624
|
-
): Promise<
|
|
625
|
-
| Extract<MatchResult, { ok: false }>
|
|
626
|
-
| (Extract<MatchResult, { ok: true }> & { tickets: CloseFixedResult | null; lines: string[] })
|
|
627
|
-
> {
|
|
628
|
-
const r = findings.match({ key: args.key, pairs: args.pairs ?? [] });
|
|
629
|
-
if (!r.ok) return r;
|
|
630
|
-
const lines = [
|
|
631
|
-
`Settled: ${r.matched} matched (state kept), ${r.fixed} fixed, ${r.new} new` +
|
|
632
|
-
(r.dismissed_unreported > 0
|
|
633
|
-
? `, ${r.dismissed_unreported} dismissed kept dismissed (stamped no longer reported)`
|
|
634
|
-
: "") +
|
|
635
|
-
".",
|
|
636
|
-
];
|
|
637
|
-
let tickets: CloseFixedResult | null = null;
|
|
638
|
-
if (afterMatch && r.fixed > 0) {
|
|
639
|
-
tickets = await afterMatch();
|
|
640
|
-
if (tickets.closed.length > 0)
|
|
641
|
-
lines.push(`Closed as fixed: ${tickets.closed.map((c) => c.ref).join(", ")}.`);
|
|
642
|
-
for (const p of tickets.problems) lines.push(`! ticket not closed: ${p}`);
|
|
643
|
-
}
|
|
644
|
-
return { ...r, tickets, lines };
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/** A stored proposal's summary, as a propose answers it. */
|
|
648
|
-
export function proposalDetails(p: StoredStage2Proposal): {
|
|
649
|
-
key: string;
|
|
650
|
-
sha: string;
|
|
651
|
-
cap_usd: number;
|
|
652
|
-
runnable: number;
|
|
653
|
-
rows: number;
|
|
654
|
-
depth: "standard" | "deep";
|
|
655
|
-
floor_rows: number;
|
|
656
|
-
floor_usd: number;
|
|
657
|
-
} {
|
|
658
|
-
const floor = p.rows.filter((x) => x.floor);
|
|
659
|
-
return {
|
|
660
|
-
key: p.key,
|
|
661
|
-
sha: p.sha,
|
|
662
|
-
cap_usd: p.cap_usd,
|
|
663
|
-
runnable: p.runnable,
|
|
664
|
-
rows: p.rows.length,
|
|
665
|
-
depth: p.depth ?? "standard",
|
|
666
|
-
floor_rows: floor.length,
|
|
667
|
-
floor_usd: floor.filter((x) => x.status === "run").reduce((n, x) => n + x.estimate_usd, 0),
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
/** The line a stage-2 start answers: how many rows run (of how many, when
|
|
672
|
-
* narrowed), and the hard stop. */
|
|
673
|
-
export function stage2StartedLine(
|
|
674
|
-
run: ScanRun,
|
|
675
|
-
runnable: number | null,
|
|
676
|
-
narrowed: boolean,
|
|
677
|
-
verbs: ScanVerbs = TOOL_VERBS,
|
|
678
|
-
): string {
|
|
679
|
-
const count =
|
|
680
|
-
narrowed && runnable !== null
|
|
681
|
-
? `${run.cases.length} row(s) of ${runnable} (${run.cases.map((c) => c.id).join(", ")})`
|
|
682
|
-
: `${run.cases.length} row(s)`;
|
|
683
|
-
return (
|
|
684
|
-
`Stage 2 ${run.id} started at ${run.sha.slice(0, 12)}: ${count}, hard stop ${usd(run.cap_usd)}. ` +
|
|
685
|
-
`It runs in the background — ${verbs.status} shows progress, ${verbs.stop} stops it.`
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/** The line a stage-1 start answers. */
|
|
690
|
-
export function startedLine(run: ScanRun, verbs: ScanVerbs = TOOL_VERBS): string {
|
|
691
|
-
return (
|
|
692
|
-
`Scan ${run.id} (${run.mode}) started at ${run.sha.slice(0, 12)}: ${run.cases.length} case(s), hard stop ${usd(run.cap_usd)}. ` +
|
|
693
|
-
`It runs in the background — ${verbs.status} shows progress, ${verbs.stop} stops it.`
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/** How often a stop through the channel looks for its answer, and how long
|
|
698
|
-
* it waits for one. */
|
|
699
|
-
export const STOP_POLL_MS = 2000;
|
|
700
|
-
export const STOP_WAIT_MS = 15_000;
|
|
701
|
-
|
|
702
|
-
/** Where a stop of a run another process drives goes: the stop channel
|
|
703
|
-
* under the state home, answered by whichever process holds the run. */
|
|
704
|
-
export interface StopChannel {
|
|
705
|
-
home: string;
|
|
706
|
-
by?: ScanStopRequest["by"];
|
|
707
|
-
sleep?: (ms: number) => Promise<void>;
|
|
708
|
-
now?: () => number;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/** Stop the recorded run: here when this runner drives it, else through the
|
|
712
|
-
* stop channel the driving process answers — the companion, or a
|
|
713
|
-
* `qcr scan` driver. No answer within STOP_WAIT_MS: nothing drives it. */
|
|
714
|
-
export async function stopScan(
|
|
715
|
-
scan: ScanRunner,
|
|
716
|
-
ch: StopChannel,
|
|
717
|
-
): Promise<{ ok: true; run: string; answer: string } | { ok: false; errors: string[] }> {
|
|
718
|
-
const sleep = ch.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
|
|
719
|
-
const now = ch.now ?? (() => Date.now());
|
|
720
|
-
const s = scan.status();
|
|
721
|
-
const run = s.run;
|
|
722
|
-
if (run === null || run.state !== "running")
|
|
723
|
-
return { ok: false, errors: ["no scan is recorded as running"] };
|
|
724
|
-
if (s.active) {
|
|
725
|
-
const r = scan.stop();
|
|
726
|
-
if (!r.ok) return r;
|
|
727
|
-
return { ok: true, run: r.id, answer: `scan ${r.id} stopped — what finished is in the ledger` };
|
|
728
|
-
}
|
|
729
|
-
const asked = clickStopScan(ch.home, run.id, new Date(now()), ch.by ?? "companion");
|
|
730
|
-
if (!asked.ok) return { ok: false, errors: [asked.line] };
|
|
731
|
-
const t0 = now();
|
|
732
|
-
for (;;) {
|
|
733
|
-
const answer = scanStopOutcome(ch.home, asked.request);
|
|
734
|
-
if (answer !== null) {
|
|
735
|
-
const read = readStopResult(ch.home);
|
|
736
|
-
const ok =
|
|
737
|
-
read.kind === "read" && read.result.request === asked.request && read.result.ok === true;
|
|
738
|
-
return ok ? { ok: true, run: run.id, answer } : { ok: false, errors: [answer] };
|
|
739
|
-
}
|
|
740
|
-
if (now() - t0 >= STOP_WAIT_MS)
|
|
741
|
-
return {
|
|
742
|
-
ok: false,
|
|
743
|
-
errors: [
|
|
744
|
-
`no process answered the stop of ${run.id} within ${STOP_WAIT_MS / 1000}s — nothing is driving it ` +
|
|
745
|
-
"(it reads as interrupted); preview and start again to resume",
|
|
746
|
-
],
|
|
747
|
-
};
|
|
748
|
-
await sleep(STOP_POLL_MS);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
78
|
+
// The rendering and the shared verbs are
|
|
79
|
+
// packages/pi-plugin/src/scan/scanrender.ts's, shared with `qcr scan` and the
|
|
80
|
+
// pi plugin's /qcr-scan.
|
|
81
|
+
export {
|
|
82
|
+
CLI_VERBS,
|
|
83
|
+
FINDINGS_LIMIT,
|
|
84
|
+
FINDINGS_LIMIT_MAX,
|
|
85
|
+
FINDING_STATES,
|
|
86
|
+
STOP_POLL_MS,
|
|
87
|
+
STOP_WAIT_MS,
|
|
88
|
+
TOOL_VERBS,
|
|
89
|
+
answerMatch,
|
|
90
|
+
byValidity,
|
|
91
|
+
findingsFilter,
|
|
92
|
+
listFindings,
|
|
93
|
+
previewDetails,
|
|
94
|
+
proposalDetails,
|
|
95
|
+
renderFindings,
|
|
96
|
+
renderPreview,
|
|
97
|
+
renderProposal,
|
|
98
|
+
renderQuestion,
|
|
99
|
+
renderStatus,
|
|
100
|
+
renderSuggestions,
|
|
101
|
+
stage2StartedLine,
|
|
102
|
+
startedDetails,
|
|
103
|
+
startedLine,
|
|
104
|
+
stopScan,
|
|
105
|
+
type ScanVerbs,
|
|
106
|
+
type StopChannel,
|
|
107
|
+
type TicketsClosed,
|
|
108
|
+
} from "@quickchatai/pi-quickreview/src/scan/scanrender.js";
|
|
751
109
|
|
|
752
110
|
// ---- tool belt ---------------------------------------------------------------
|
|
753
111
|
|
|
@@ -1119,11 +477,12 @@ export function scanLeafRunner(
|
|
|
1119
477
|
};
|
|
1120
478
|
}
|
|
1121
479
|
|
|
1122
|
-
/** The scan runner the companion's belt drives
|
|
480
|
+
/** The scan runner the companion's belt drives
|
|
481
|
+
* (packages/pi-plugin/src/scan/scanassemble.ts): the reviewed checkout as a
|
|
1123
482
|
* source of trees, the state home for every record, the saved unit cut from
|
|
1124
483
|
* the setup engine, the live config under the profile, and a case runner
|
|
1125
|
-
* over the scan agent build — and the triage over its findings:
|
|
1126
|
-
*
|
|
484
|
+
* over the scan agent build — and the triage over its findings: reviewer
|
|
485
|
+
* memory and the tracker sink are the engine's own, handed in. */
|
|
1127
486
|
export function buildScan(
|
|
1128
487
|
engine: SetupEngine,
|
|
1129
488
|
reviewedRepo: string,
|
|
@@ -1145,75 +504,38 @@ export function buildScan(
|
|
|
1145
504
|
} {
|
|
1146
505
|
const root = env.QCR_ROOT?.trim() || sandboxRoot();
|
|
1147
506
|
const home = stateHome(env);
|
|
1148
|
-
const state = stateHomeFs(home);
|
|
1149
|
-
const agentsDir = join(home, SCAN_AGENTS_DIR);
|
|
1150
|
-
const settingsOf = (): ScanSettings | null => {
|
|
1151
|
-
const loaded = loadScanConfig(root, env);
|
|
1152
|
-
return loaded.ok ? scanSettings(loaded.cfg) : null;
|
|
1153
|
-
};
|
|
1154
|
-
const findings = new FindingsStore(state);
|
|
1155
|
-
const transcriptDir = join(home, SCAN_TRANSCRIPTS_DIR);
|
|
1156
|
-
// A token a case would not outlive stops the run, on either host.
|
|
1157
|
-
const stopOnExpired = (reason: string): void => {
|
|
1158
|
-
console.error(`scan: stopping — ${reason}`);
|
|
1159
|
-
scan.stop();
|
|
1160
|
-
};
|
|
1161
507
|
// Assigned below: the triage reads the runner's runs, the runner closes
|
|
1162
508
|
// the triage's tickets at every run's end.
|
|
1163
509
|
let triage: ScanTriage | null = null;
|
|
1164
|
-
const scan =
|
|
1165
|
-
|
|
1166
|
-
|
|
510
|
+
const { scan, findings, settingsOf } = assembleScan({
|
|
511
|
+
home,
|
|
512
|
+
reviewedRepo,
|
|
513
|
+
root,
|
|
514
|
+
config: () => loadScanConfig(root, env),
|
|
515
|
+
profiles: homeProfileLookup(env),
|
|
516
|
+
unitCut: engine,
|
|
1167
517
|
// In the scan container: each case on a fresh refresh-disabled copy of
|
|
1168
518
|
// the companion's login; a token a case would not outlive stops the run.
|
|
1169
|
-
cases:
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
519
|
+
cases: ({ agentsDir, transcriptDir, stop }) =>
|
|
520
|
+
scanCasesForEnv(
|
|
521
|
+
scanLeafRunner(
|
|
522
|
+
piCaseRunner({ agentsDir, cwd: root, transcriptDir }),
|
|
523
|
+
root,
|
|
524
|
+
env,
|
|
525
|
+
sdkLeaves &&
|
|
526
|
+
(() => sdkLeaves({ agentsDir, cwd: root, transcriptDir, env, onExpired: stop })),
|
|
527
|
+
),
|
|
1173
528
|
env,
|
|
1174
|
-
|
|
1175
|
-
(() => sdkLeaves({ agentsDir, cwd: root, transcriptDir, env, onExpired: stopOnExpired })),
|
|
529
|
+
stop,
|
|
1176
530
|
),
|
|
1177
|
-
env,
|
|
1178
|
-
stopOnExpired,
|
|
1179
|
-
),
|
|
1180
|
-
source: gitScanSource(reviewedRepo, home),
|
|
1181
|
-
savedCut: () => {
|
|
1182
|
-
const cut = engine.savedCut();
|
|
1183
|
-
if (cut) return { ok: true, cut };
|
|
1184
|
-
const status = engine.status();
|
|
1185
|
-
return {
|
|
1186
|
-
ok: false,
|
|
1187
|
-
errors: status.cut_file_unreadable
|
|
1188
|
-
? status.problems
|
|
1189
|
-
: ["no saved unit cut — a scan reads by unit; propose, adjust and save one first"],
|
|
1190
|
-
};
|
|
1191
|
-
},
|
|
1192
|
-
settings: () => {
|
|
1193
|
-
const loaded = loadScanConfig(root, env);
|
|
1194
|
-
return loaded.ok ? { ok: true, settings: scanSettings(loaded.cfg) } : loaded;
|
|
1195
|
-
},
|
|
1196
|
-
// At the depth the runner planned at — a stage-2 proposal's own, which
|
|
1197
|
-
// its `deep` may have set apart from `[scan] depth`.
|
|
1198
|
-
buildAgents: (settings) => {
|
|
1199
|
-
const loaded = loadScanConfig(root, env);
|
|
1200
|
-
if (!loaded.ok) return loaded;
|
|
1201
|
-
const cfg = { ...loaded.cfg, scan: { ...loaded.cfg.scan, depth: settings.scan.depth } };
|
|
1202
|
-
try {
|
|
1203
|
-
buildScanAgents(root, agentsDir, cfg, homeProfileLookup(env));
|
|
1204
|
-
return { ok: true };
|
|
1205
|
-
} catch (err) {
|
|
1206
|
-
return { ok: false, errors: [err instanceof Error ? err.message : String(err)] };
|
|
1207
|
-
}
|
|
1208
|
-
},
|
|
1209
531
|
closeFixedTickets: async () => (triage ? triage.closeFixed() : { closed: [], problems: [] }),
|
|
1210
|
-
lease
|
|
532
|
+
lease,
|
|
1211
533
|
log: (line) => console.error(line),
|
|
1212
534
|
});
|
|
1213
535
|
if (shared) {
|
|
1214
536
|
triage = new ScanTriage({
|
|
1215
537
|
findings,
|
|
1216
|
-
state,
|
|
538
|
+
state: stateHomeFs(home),
|
|
1217
539
|
memory: shared.memory,
|
|
1218
540
|
tracker: shared.tracker,
|
|
1219
541
|
clock: () => new Date(),
|
|
@@ -1224,13 +546,4 @@ export function buildScan(
|
|
|
1224
546
|
return { scan, findings, settingsOf, triage, home };
|
|
1225
547
|
}
|
|
1226
548
|
|
|
1227
|
-
|
|
1228
|
-
* `running` by a companion that is gone reads as interrupted. */
|
|
1229
|
-
export function scanRuns(scan: ScanRunner): Array<{ id: string; sha: string; state: string }> {
|
|
1230
|
-
const s = scan.status();
|
|
1231
|
-
const out: Array<{ id: string; sha: string; state: string }> = [];
|
|
1232
|
-
if (s.run) out.push({ id: s.run.id, sha: s.run.sha, state: s.run.display });
|
|
1233
|
-
for (const h of s.history)
|
|
1234
|
-
if (!out.some((r) => r.id === h.id)) out.push({ id: h.id, sha: h.sha, state: h.state });
|
|
1235
|
-
return out;
|
|
1236
|
-
}
|
|
549
|
+
export { scanRuns };
|