@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/dist/src/setup/stage2.js
DELETED
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
import { globMatcher } from "@quickchatai/pi-quickreview/src/glob.js";
|
|
2
|
-
import { isSweepSkipped, isTestFile } from "@quickchatai/pi-quickreview/src/heuristics.js";
|
|
3
|
-
import { lensEnabled, flooredTier, } from "@quickchatai/pi-quickreview/src/lenstuning.js";
|
|
4
|
-
import { PASS_TIER_ORDER, tiersAtOrAbove, } from "@quickchatai/pi-quickreview/src/types.js";
|
|
5
|
-
import { caseEstimate, coverKey, cutRanges, focusLine, planKey, rangeNote, TREE_PATHS_NOTE, rangesCover, SCAN_DEEP_LENSES, stage1Exclusion, SCAN_PASS, } from "./scan.js";
|
|
6
|
-
import { depthOf, measuredRate, rateWhere } from "./ledger.js";
|
|
7
|
-
import { assign, splitUnit } from "./unitcut.js";
|
|
8
|
-
/** At most this many rows the companion asks for in one proposal (the deep
|
|
9
|
-
* floor is not counted: it is bounded by the tree). */
|
|
10
|
-
export const MAX_STAGE2_ROWS = 100;
|
|
11
|
-
/** At most this many tests paired with one code file (the nearest win). */
|
|
12
|
-
export const MAX_PAIRED_PER_FILE = 3;
|
|
13
|
-
/** A row's reason is one line. */
|
|
14
|
-
export const ROW_REASON_MAX = 300;
|
|
15
|
-
/** The unit a file no unit of the cut owns is filed under. */
|
|
16
|
-
export const NO_UNIT = "(no unit)";
|
|
17
|
-
/** Better bases first: what `priceStage2Row` prefers at the row's own depth. */
|
|
18
|
-
const BASIS_RANK = {
|
|
19
|
-
measured: 0,
|
|
20
|
-
borrowed: 1,
|
|
21
|
-
learned: 2,
|
|
22
|
-
extrapolated: 3,
|
|
23
|
-
};
|
|
24
|
-
/** Stage 1 measured at BOTH validator settings at one tier and effort —
|
|
25
|
-
* `[scan]`'s own when it has both, else the first other pair (sorted) that
|
|
26
|
-
* does — or null: the skeptic's share is then unmeasured. */
|
|
27
|
-
function stage1Pair(entries, cfg, validator) {
|
|
28
|
-
const at = (tier, effort) => {
|
|
29
|
-
const row = measuredRate(entries, { lens: SCAN_PASS, tier, effort, validator });
|
|
30
|
-
const other = measuredRate(entries, { lens: SCAN_PASS, tier, effort, validator: !validator });
|
|
31
|
-
return row && other ? { row, other } : null;
|
|
32
|
-
};
|
|
33
|
-
const own = at(cfg.tier, cfg.effort);
|
|
34
|
-
if (own)
|
|
35
|
-
return own;
|
|
36
|
-
const keys = [
|
|
37
|
-
...new Set(entries.filter((e) => e.lens === SCAN_PASS).map((e) => `${e.tier}\0${e.effort}`)),
|
|
38
|
-
].sort();
|
|
39
|
-
for (const k of keys) {
|
|
40
|
-
const [tier, effort] = k.split("\0");
|
|
41
|
-
const p = at(tier, effort);
|
|
42
|
-
if (p)
|
|
43
|
-
return p;
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* A stage-2 row's price at `lens`/`tier`/`effort` from the ledger (header
|
|
49
|
-
* "PRICED FROM THE LEDGER"), or null when stage 1 has measured nothing. With
|
|
50
|
-
* a `depth`, the price stands on readings at that depth when they price the
|
|
51
|
-
* row at least as well as every reading does; otherwise it is the price over
|
|
52
|
-
* every reading, marked `across_depth`.
|
|
53
|
-
*/
|
|
54
|
-
export function priceStage2Row(entries, cfg, lens, tier, effort, depth) {
|
|
55
|
-
const any = priceFrom(entries, cfg, lens, tier, effort);
|
|
56
|
-
if (depth === undefined || any === null)
|
|
57
|
-
return any;
|
|
58
|
-
const same = priceFrom(entries.filter((e) => depthOf(e) === depth), cfg, lens, tier, effort);
|
|
59
|
-
if (same !== null && BASIS_RANK[same.basis] <= BASIS_RANK[any.basis])
|
|
60
|
-
return same;
|
|
61
|
-
return { ...any, across_depth: true };
|
|
62
|
-
}
|
|
63
|
-
function priceFrom(entries, cfg, lens, tier, effort) {
|
|
64
|
-
const validator = cfg.validator;
|
|
65
|
-
const own = measuredRate(entries, { lens, tier, effort, validator });
|
|
66
|
-
if (own)
|
|
67
|
-
return { rate: own, basis: "measured", from: lens };
|
|
68
|
-
// Borrowed only from another DEEP lens: pass-scan is the cheap wide pass,
|
|
69
|
-
// and its rate at a matching tier/effort (a profile giving a deep lens
|
|
70
|
-
// `effort = "low"` at `[scan] tier`) would price a deep row at stage 1's
|
|
71
|
-
// cost unscaled — stage 1's rate reaches a row only through a factor.
|
|
72
|
-
const deepAt = (v, same) => (e) => (same ? e.lens === lens : e.lens !== lens && e.lens !== SCAN_PASS) &&
|
|
73
|
-
e.tier === tier &&
|
|
74
|
-
e.effort === effort &&
|
|
75
|
-
e.validator === v;
|
|
76
|
-
const lensesOf = (keep) => [...new Set(entries.filter((e) => keep(e) && e.cost_usd !== null).map((e) => e.lens))]
|
|
77
|
-
.sort()
|
|
78
|
-
.join(", ");
|
|
79
|
-
const borrowed = rateWhere(entries, deepAt(validator, false));
|
|
80
|
-
if (borrowed)
|
|
81
|
-
return { rate: borrowed, basis: "borrowed", from: lensesOf(deepAt(validator, false)) };
|
|
82
|
-
// Stage 1 at a validator setting: at `[scan]`'s tier and effort, else at
|
|
83
|
-
// any (the extrapolation's fallback too).
|
|
84
|
-
const stage1At = (v) => measuredRate(entries, { lens: SCAN_PASS, tier: cfg.tier, effort: cfg.effort, validator: v }) ??
|
|
85
|
-
rateWhere(entries, (e) => e.lens === SCAN_PASS && e.validator === v);
|
|
86
|
-
// Learned: a stage-2 measurement at the same tier and effort with the
|
|
87
|
-
// validator the other way, carried across by the skeptic's measured share —
|
|
88
|
-
// stage 1's rate at the row's setting over its rate at the other. Both
|
|
89
|
-
// stage-1 rates must come from ONE tier and effort (`[scan]`'s first, else
|
|
90
|
-
// the first other pair measured both ways): a ratio across tiers would be
|
|
91
|
-
// the tier's price, not the skeptic's. The share is bounded by direction —
|
|
92
|
-
// the skeptic adds work, so it can raise a price learned from a skeptic-off
|
|
93
|
-
// measurement and lower one learned from a skeptic-on one, never the
|
|
94
|
-
// reverse (two noisy stage-1 samples must not price a validated row UNDER
|
|
95
|
-
// what the unvalidated lens was measured at).
|
|
96
|
-
const other = !validator;
|
|
97
|
-
const s1Row = stage1At(validator);
|
|
98
|
-
const pair = stage1Pair(entries, cfg, validator);
|
|
99
|
-
if (pair && pair.other.usd_per_kline > 0) {
|
|
100
|
-
const raw = pair.row.usd_per_kline / pair.other.usd_per_kline;
|
|
101
|
-
const skeptic = validator ? Math.max(raw, 1) : Math.min(raw, 1);
|
|
102
|
-
for (const same of [true, false]) {
|
|
103
|
-
const s2 = rateWhere(entries, deepAt(other, same));
|
|
104
|
-
if (!s2)
|
|
105
|
-
continue;
|
|
106
|
-
return {
|
|
107
|
-
rate: { ...s2, usd_per_kline: s2.usd_per_kline * skeptic },
|
|
108
|
-
basis: "learned",
|
|
109
|
-
from: lensesOf(deepAt(other, same)),
|
|
110
|
-
learned: { validator: other, factor: s2.usd_per_kline / pair.other.usd_per_kline, skeptic },
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const stage1 = s1Row ?? rateWhere(entries, (e) => e.lens === SCAN_PASS);
|
|
115
|
-
if (!stage1)
|
|
116
|
-
return null;
|
|
117
|
-
return {
|
|
118
|
-
rate: { ...stage1, usd_per_kline: stage1.usd_per_kline * cfg.unmeasured_rate_factor },
|
|
119
|
-
basis: "extrapolated",
|
|
120
|
-
from: SCAN_PASS,
|
|
121
|
-
factor: cfg.unmeasured_rate_factor,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
/** A row's effort as the ledger keys it: the lens's profile `effort`, or
|
|
125
|
-
* `tier` (the tier's own reasoning level). */
|
|
126
|
-
export function lensEffort(lenses, lens) {
|
|
127
|
-
return lenses?.[lens]?.effort ?? "tier";
|
|
128
|
-
}
|
|
129
|
-
/** A risk pass runs at `sol-high` alone; any other lens, null. */
|
|
130
|
-
function riskTier(riskPasses, lens) {
|
|
131
|
-
return riskPasses.includes(lens) ? "sol-high" : null;
|
|
132
|
-
}
|
|
133
|
-
/** The tiers `lens` may run at in a scan: the ones its build variants exist
|
|
134
|
-
* at, at or above its profile floor. */
|
|
135
|
-
export function lensTiers(lenses, lens, riskPasses) {
|
|
136
|
-
const risk = riskTier(riskPasses, lens);
|
|
137
|
-
if (risk !== null)
|
|
138
|
-
return [risk];
|
|
139
|
-
const floor = flooredTier(lenses, lens, PASS_TIER_ORDER[0]);
|
|
140
|
-
return tiersAtOrAbove(floor);
|
|
141
|
-
}
|
|
142
|
-
/** The deep lenses stage 2 may run on this repo: stage 1's suggestible set,
|
|
143
|
-
* less every lens the profile switched off. */
|
|
144
|
-
export function stage2Lenses(lenses) {
|
|
145
|
-
return SCAN_DEEP_LENSES.filter((l) => lensEnabled(lenses, l, true));
|
|
146
|
-
}
|
|
147
|
-
// ---- pairing tests -------------------------------------------------------------
|
|
148
|
-
const TEST_DIRS = new Set(["test", "tests", "__tests__", "spec", "specs", "testing", "main"]);
|
|
149
|
-
const FAMILY = {
|
|
150
|
-
ts: "js",
|
|
151
|
-
tsx: "js",
|
|
152
|
-
js: "js",
|
|
153
|
-
jsx: "js",
|
|
154
|
-
mjs: "js",
|
|
155
|
-
cjs: "js",
|
|
156
|
-
mts: "js",
|
|
157
|
-
cts: "js",
|
|
158
|
-
};
|
|
159
|
-
function ext(path) {
|
|
160
|
-
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
161
|
-
const i = base.lastIndexOf(".");
|
|
162
|
-
return i <= 0 ? "" : base.slice(i + 1).toLowerCase();
|
|
163
|
-
}
|
|
164
|
-
function family(path) {
|
|
165
|
-
const e = ext(path);
|
|
166
|
-
return FAMILY[e] ?? e;
|
|
167
|
-
}
|
|
168
|
-
function dirSegs(path) {
|
|
169
|
-
const i = path.lastIndexOf("/");
|
|
170
|
-
return i < 0 ? [] : path.slice(0, i).split("/");
|
|
171
|
-
}
|
|
172
|
-
/** A code file's subject: its basename without the extension. */
|
|
173
|
-
function codeStem(path) {
|
|
174
|
-
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
175
|
-
const i = base.lastIndexOf(".");
|
|
176
|
-
return (i <= 0 ? base : base.slice(0, i)).toLowerCase();
|
|
177
|
-
}
|
|
178
|
-
/** A test file's subject: its basename with the extension and every test
|
|
179
|
-
* marker (`test_`, `_test`, `.test`, `.spec`, `Test`, …) stripped. */
|
|
180
|
-
function testStem(path) {
|
|
181
|
-
let s = path.slice(path.lastIndexOf("/") + 1);
|
|
182
|
-
const i = s.lastIndexOf(".");
|
|
183
|
-
if (i > 0)
|
|
184
|
-
s = s.slice(0, i);
|
|
185
|
-
s = s
|
|
186
|
-
.replace(/[._-](test|spec)s?$/i, "")
|
|
187
|
-
.replace(/(?<=[a-z0-9])Tests?$/, "")
|
|
188
|
-
.replace(/^tests?[_-]/i, "");
|
|
189
|
-
return s.toLowerCase();
|
|
190
|
-
}
|
|
191
|
-
const endsWithSegs = (a, b) => b.length <= a.length && b.every((s, i) => a[a.length - b.length + i] === s);
|
|
192
|
-
/**
|
|
193
|
-
* The tests in `tree` that pair with `code`: same subject, same language
|
|
194
|
-
* family, and a directory that mirrors the code's once the test directories
|
|
195
|
-
* (`tests/`, `__tests__/`, `src/test` vs `src/main`, …) are set aside — the
|
|
196
|
-
* code's own directory, a `tests/` beside or under it, or a top-level tests
|
|
197
|
-
* tree mirroring its package. The nearest (most shared leading directories)
|
|
198
|
-
* win, at most `MAX_PAIRED_PER_FILE` of those `eligible` says a lens may read
|
|
199
|
-
* — so a test it may not never takes a nearer one's place. A heuristic,
|
|
200
|
-
* deliberately: a pair is reading context for the lens, never a claim about
|
|
201
|
-
* coverage.
|
|
202
|
-
*/
|
|
203
|
-
export function pairTests(code, tree, eligible = () => true) {
|
|
204
|
-
const stem = codeStem(code);
|
|
205
|
-
if (stem === "" || isTestFile(code))
|
|
206
|
-
return [];
|
|
207
|
-
const fam = family(code);
|
|
208
|
-
const cDir = dirSegs(code);
|
|
209
|
-
const cNorm = cDir.filter((s) => !TEST_DIRS.has(s.toLowerCase()));
|
|
210
|
-
const scored = [];
|
|
211
|
-
for (const t of tree) {
|
|
212
|
-
if (t === code || !isTestFile(t) || family(t) !== fam || testStem(t) !== stem)
|
|
213
|
-
continue;
|
|
214
|
-
if (!eligible(t))
|
|
215
|
-
continue;
|
|
216
|
-
const tDir = dirSegs(t);
|
|
217
|
-
const tNorm = tDir.filter((s) => !TEST_DIRS.has(s.toLowerCase()));
|
|
218
|
-
if (!(endsWithSegs(cNorm, tNorm) || endsWithSegs(tNorm, cNorm)))
|
|
219
|
-
continue;
|
|
220
|
-
let shared = 0;
|
|
221
|
-
while (shared < cDir.length && shared < tDir.length && cDir[shared] === tDir[shared])
|
|
222
|
-
shared++;
|
|
223
|
-
scored.push({ path: t, score: shared * 10 - Math.abs(tNorm.length - cNorm.length) });
|
|
224
|
-
}
|
|
225
|
-
scored.sort((a, b) => b.score - a.score || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
226
|
-
return scored.slice(0, MAX_PAIRED_PER_FILE).map((s) => s.path);
|
|
227
|
-
}
|
|
228
|
-
// ---- the plan ------------------------------------------------------------------
|
|
229
|
-
const cleanPath = (p) => p
|
|
230
|
-
.trim()
|
|
231
|
-
.replace(/^`+|`+$/g, "")
|
|
232
|
-
.replace(/^\.?\/+/, "");
|
|
233
|
-
/** A row's tier when none is named: the lens's floor over `[scan] tier`
|
|
234
|
-
* (`sol-high` for a risk pass). */
|
|
235
|
-
function defaultRowTier(input, lens) {
|
|
236
|
-
return riskTier(input.riskPasses, lens) ?? flooredTier(input.lenses, lens, input.defaultTier);
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Plan a proposal: every row checked (all-or-nothing — one bad row refuses
|
|
240
|
-
* the proposal, each problem named), tests paired, units found, each row
|
|
241
|
-
* priced, covered rows marked; then, for a deep proposal, the floor rows
|
|
242
|
-
* after them; then the runnable rows fitted to the budget in that order.
|
|
243
|
-
* Deterministic.
|
|
244
|
-
*/
|
|
245
|
-
export function planStage2(input) {
|
|
246
|
-
const errors = [];
|
|
247
|
-
if (input.rows.length === 0 && input.floor === undefined)
|
|
248
|
-
return { ok: false, errors: ["a stage 2 proposal needs at least one row"] };
|
|
249
|
-
if (input.rows.length > MAX_STAGE2_ROWS) {
|
|
250
|
-
return {
|
|
251
|
-
ok: false,
|
|
252
|
-
errors: [`${input.rows.length} rows — at most ${MAX_STAGE2_ROWS} in one proposal`],
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
const excluded = globMatcher(input.exclude);
|
|
256
|
-
const blobs = new Map(input.files.map((f) => [f.path, f.blob]));
|
|
257
|
-
const treePaths = [...blobs.keys()].sort();
|
|
258
|
-
const unitOf = unitOwner(input.cut, treePaths);
|
|
259
|
-
const allowed = stage2Lenses(input.lenses);
|
|
260
|
-
const seen = new Set();
|
|
261
|
-
const rows = [];
|
|
262
|
-
/** One file too big for one case alone: read in line ranges. */
|
|
263
|
-
const oversized = (sorted, lines) => sorted.length === 1 && lines.get(sorted[0]) > input.budget.maxLines;
|
|
264
|
-
/** One row over `sorted` (checked files; `lines` holds their counts), its
|
|
265
|
-
* tests paired when `pair` — or why it cannot be priced. */
|
|
266
|
-
const build = (spec) => {
|
|
267
|
-
const { lens, sorted, lines } = spec;
|
|
268
|
-
let rowLines = sorted.reduce((n, p) => n + lines.get(p), 0);
|
|
269
|
-
const oversize = oversized(sorted, lines);
|
|
270
|
-
// Pair the tests, nearest first, while the case budget holds. A ranged
|
|
271
|
-
// row's tests ride in EVERY range case, so they get at most half a case.
|
|
272
|
-
const testFiles = !spec.pair
|
|
273
|
-
? 0
|
|
274
|
-
: oversize
|
|
275
|
-
? input.budget.maxFiles - 1
|
|
276
|
-
: input.budget.maxFiles - sorted.length;
|
|
277
|
-
const testLines = oversize
|
|
278
|
-
? Math.floor(input.budget.maxLines / 2)
|
|
279
|
-
: input.budget.maxLines - rowLines;
|
|
280
|
-
let pairedLines = 0;
|
|
281
|
-
const tests = [];
|
|
282
|
-
const unpaired = [];
|
|
283
|
-
const taken = new Set(sorted);
|
|
284
|
-
// A paired test obeys the named files' rules: never an unreadable kind
|
|
285
|
-
// (generated, vendored, a lockfile), never excluded, readable as text.
|
|
286
|
-
const pairable = (t) => !isSweepSkipped(t) && !excluded(t) && input.linesOf(t) !== null;
|
|
287
|
-
for (const code of spec.pair ? sorted : []) {
|
|
288
|
-
for (const t of pairTests(code, treePaths, pairable)) {
|
|
289
|
-
if (taken.has(t))
|
|
290
|
-
continue;
|
|
291
|
-
taken.add(t);
|
|
292
|
-
const n = input.linesOf(t);
|
|
293
|
-
if (tests.length + 1 > testFiles || pairedLines + n > testLines) {
|
|
294
|
-
unpaired.push(t);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
tests.push({ path: t, blob: blobs.get(t), lines: n });
|
|
298
|
-
pairedLines += n;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
let ranges;
|
|
302
|
-
if (oversize) {
|
|
303
|
-
const p = sorted[0];
|
|
304
|
-
const done = input.partial?.(lens).get(coverKey(p, blobs.get(p))) ?? [];
|
|
305
|
-
ranges = cutRanges(lines.get(p), input.budget.maxLines - pairedLines, input.textOf?.(p) ?? null).filter((r) => !rangesCover(done, r.start, r.end));
|
|
306
|
-
rowLines = ranges.reduce((n, r) => n + rangeCaseLines(r, pairedLines), 0);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
rowLines += pairedLines;
|
|
310
|
-
}
|
|
311
|
-
const effort = lensEffort(input.lenses, lens);
|
|
312
|
-
const price = input.price(lens, spec.tier, effort);
|
|
313
|
-
if (price === null) {
|
|
314
|
-
return `no measured price for any scan lens yet — run stage 1 (its calibration run prices the scan) before proposing stage 2`;
|
|
315
|
-
}
|
|
316
|
-
const files = sorted.map((p) => ({ path: p, blob: blobs.get(p), lines: lines.get(p) }));
|
|
317
|
-
const cover = input.covered(lens);
|
|
318
|
-
const all = [...files, ...tests];
|
|
319
|
-
const isCovered = all.every((f) => cover.has(coverKey(f.path, f.blob))) ||
|
|
320
|
-
(ranges !== undefined && ranges.length === 0);
|
|
321
|
-
return {
|
|
322
|
-
id: spec.id,
|
|
323
|
-
lens,
|
|
324
|
-
tier: spec.tier,
|
|
325
|
-
effort,
|
|
326
|
-
reason: spec.reason,
|
|
327
|
-
units: [...new Set(all.map((f) => unitOf(f.path)))].sort(),
|
|
328
|
-
files,
|
|
329
|
-
tests,
|
|
330
|
-
unpaired,
|
|
331
|
-
...(ranges !== undefined ? { ranges } : {}),
|
|
332
|
-
lines: rowLines,
|
|
333
|
-
estimate_usd: isCovered
|
|
334
|
-
? 0
|
|
335
|
-
: ranges !== undefined
|
|
336
|
-
? ranges.reduce((n, r) => n + caseEstimate(rangeCaseLines(r, pairedLines), price.rate), 0)
|
|
337
|
-
: caseEstimate(rowLines, price.rate),
|
|
338
|
-
price,
|
|
339
|
-
status: isCovered ? "covered" : "run",
|
|
340
|
-
...(spec.floor ? { floor: true } : {}),
|
|
341
|
-
};
|
|
342
|
-
};
|
|
343
|
-
input.rows.forEach((req, i) => {
|
|
344
|
-
const id = `row-${i + 1}`;
|
|
345
|
-
const bad = (why) => void errors.push(`${id}: ${why}`);
|
|
346
|
-
const lens = typeof req?.lens === "string" ? req.lens.trim() : "";
|
|
347
|
-
if (lens === SCAN_PASS)
|
|
348
|
-
return bad(`${SCAN_PASS} is stage 1's lens — a stage 2 row names a deep lens`);
|
|
349
|
-
if (!allowed.includes(lens)) {
|
|
350
|
-
return bad(SCAN_DEEP_LENSES.includes(lens)
|
|
351
|
-
? `${JSON.stringify(lens)} is switched off by this repo's profile`
|
|
352
|
-
: `${JSON.stringify(lens)} is not a deep lens a scan can run (one of: ${allowed.join(", ")})`);
|
|
353
|
-
}
|
|
354
|
-
const reason = typeof req.reason === "string" ? req.reason.trim().replace(/\s+/g, " ") : "";
|
|
355
|
-
if (reason === "")
|
|
356
|
-
return bad("no reason — say in one line what the lens should chase");
|
|
357
|
-
if (reason.length > ROW_REASON_MAX)
|
|
358
|
-
return bad(`the reason is ${reason.length} characters — one line, at most ${ROW_REASON_MAX}`);
|
|
359
|
-
const tiers = lensTiers(input.lenses, lens, input.riskPasses);
|
|
360
|
-
let tier;
|
|
361
|
-
if (req.tier === undefined || req.tier === null || req.tier === "") {
|
|
362
|
-
tier = defaultRowTier(input, lens);
|
|
363
|
-
}
|
|
364
|
-
else if (tiers.includes(req.tier)) {
|
|
365
|
-
tier = req.tier;
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
return bad(`${lens} cannot run at ${JSON.stringify(req.tier)} here — one of: ${tiers.join(", ")}`);
|
|
369
|
-
}
|
|
370
|
-
const named = [
|
|
371
|
-
...new Set((Array.isArray(req.files) ? req.files : [])
|
|
372
|
-
.filter((p) => typeof p === "string")
|
|
373
|
-
.map(cleanPath)),
|
|
374
|
-
].filter((p) => p !== "");
|
|
375
|
-
if (named.length === 0)
|
|
376
|
-
return bad("no files");
|
|
377
|
-
const problems = [];
|
|
378
|
-
const lines = new Map();
|
|
379
|
-
for (const p of named) {
|
|
380
|
-
if (!blobs.has(p))
|
|
381
|
-
problems.push(`${p} is not in the tree at the scan sha`);
|
|
382
|
-
else if (isTestFile(p))
|
|
383
|
-
problems.push(`${p} is a test — tests enter stage 2 only paired with the code they test; name that code`);
|
|
384
|
-
else if (isSweepSkipped(p))
|
|
385
|
-
problems.push(`${p} is not code a lens reads (lockfile, generated, binary or prose)`);
|
|
386
|
-
else if (excluded(p))
|
|
387
|
-
problems.push(`${p} is excluded from scans by [scan] exclude`);
|
|
388
|
-
else {
|
|
389
|
-
const n = input.linesOf(p);
|
|
390
|
-
if (n === null) {
|
|
391
|
-
const why = input.whyUnreadable?.(p) ?? null;
|
|
392
|
-
problems.push(`${p} cannot be read as text${why === "binary" ? " (skipped: binary)" : why === "too-big" ? " (skipped: over the size cap)" : ""}`);
|
|
393
|
-
}
|
|
394
|
-
else
|
|
395
|
-
lines.set(p, n);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (problems.length > 0)
|
|
399
|
-
return bad(problems.join("; "));
|
|
400
|
-
const sorted = [...named].sort();
|
|
401
|
-
const dedupe = `${lens}\0${sorted.join("\0")}`;
|
|
402
|
-
if (seen.has(dedupe))
|
|
403
|
-
return bad(`the same ${lens} row as an earlier one`);
|
|
404
|
-
seen.add(dedupe);
|
|
405
|
-
const rowLines = sorted.reduce((n, p) => n + lines.get(p), 0);
|
|
406
|
-
// One file too big for one case is cut into line ranges; anywhere else a
|
|
407
|
-
// row over one case is refused.
|
|
408
|
-
const oversize = oversized(sorted, lines);
|
|
409
|
-
if (!oversize && (sorted.length > input.budget.maxFiles || rowLines > input.budget.maxLines)) {
|
|
410
|
-
const big = sorted.filter((p) => lines.get(p) > input.budget.maxLines);
|
|
411
|
-
return bad(`${sorted.length} files, ${rowLines} lines — over one case (${input.budget.maxFiles} files, ${input.budget.maxLines} lines); split it into narrower rows` +
|
|
412
|
-
(big.length > 0
|
|
413
|
-
? ` (${big.join(", ")} ${big.length === 1 ? "is" : "are"} over one case alone — name ${big.length === 1 ? "it" : "each"} alone in a row and it is cut into line ranges)`
|
|
414
|
-
: ""));
|
|
415
|
-
}
|
|
416
|
-
const row = build({ id, lens, tier, reason, sorted, lines, pair: true, floor: false });
|
|
417
|
-
if (typeof row === "string")
|
|
418
|
-
return bad(row);
|
|
419
|
-
rows.push(row);
|
|
420
|
-
});
|
|
421
|
-
if (errors.length > 0)
|
|
422
|
-
return { ok: false, errors };
|
|
423
|
-
let floor = null;
|
|
424
|
-
if (input.floor !== undefined) {
|
|
425
|
-
floor = {
|
|
426
|
-
lenses: input.floor.filter((l) => allowed.includes(l)),
|
|
427
|
-
skipped: input.floor.filter((l) => !allowed.includes(l)),
|
|
428
|
-
covered: 0,
|
|
429
|
-
named: 0,
|
|
430
|
-
};
|
|
431
|
-
// Stage 1's files: readable, not a test, a migration, an excluded glob
|
|
432
|
-
// or an unreadable kind — and owned by a unit of the cut.
|
|
433
|
-
const lines = new Map();
|
|
434
|
-
for (const p of treePaths) {
|
|
435
|
-
if (stage1Exclusion(p, excluded) !== null)
|
|
436
|
-
continue;
|
|
437
|
-
const n = input.linesOf(p);
|
|
438
|
-
if (n !== null)
|
|
439
|
-
lines.set(p, n);
|
|
440
|
-
}
|
|
441
|
-
const owned = assign(input.cut, [...lines.keys()]).byUnit;
|
|
442
|
-
let n = 0;
|
|
443
|
-
for (const lens of floor.lenses) {
|
|
444
|
-
const named = new Set(rows.filter((r) => !r.floor && r.lens === lens).flatMap((r) => r.files.map((f) => f.path)));
|
|
445
|
-
const cover = input.covered(lens);
|
|
446
|
-
const tier = defaultRowTier(input, lens);
|
|
447
|
-
for (const u of input.cut.units) {
|
|
448
|
-
const files = [];
|
|
449
|
-
for (const p of owned.get(u.name) ?? []) {
|
|
450
|
-
if (named.has(p))
|
|
451
|
-
floor.named++;
|
|
452
|
-
else if (cover.has(coverKey(p, blobs.get(p))))
|
|
453
|
-
floor.covered++;
|
|
454
|
-
else
|
|
455
|
-
files.push(p);
|
|
456
|
-
}
|
|
457
|
-
if (files.length === 0)
|
|
458
|
-
continue;
|
|
459
|
-
for (const chunk of splitUnit(u.name, files, (p) => lines.get(p), input.budget)) {
|
|
460
|
-
const row = build({
|
|
461
|
-
id: `floor-${++n}`,
|
|
462
|
-
lens,
|
|
463
|
-
tier,
|
|
464
|
-
reason: `deep floor: ${lens} over ${u.name}`,
|
|
465
|
-
sorted: [...chunk.files].sort(),
|
|
466
|
-
lines,
|
|
467
|
-
pair: false,
|
|
468
|
-
floor: true,
|
|
469
|
-
});
|
|
470
|
-
if (typeof row === "string")
|
|
471
|
-
return { ok: false, errors: [`floor-${n}: ${row}`] };
|
|
472
|
-
rows.push(row);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (rows.length === 0) {
|
|
477
|
-
return {
|
|
478
|
-
ok: false,
|
|
479
|
-
errors: [
|
|
480
|
-
floor.lenses.length === 0
|
|
481
|
-
? `no row and no floor: every [scan] deep_lenses lens (${input.floor.join(", ")}) is switched off by this repo's profile`
|
|
482
|
-
: floor.covered > 0
|
|
483
|
-
? "nothing left for the floor: the ledger already holds a deep reading of every file by every floor lens at this content"
|
|
484
|
-
: "no row and no floor: stage 1 reads no file a unit of the cut owns",
|
|
485
|
-
],
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
let total = 0;
|
|
490
|
-
let over = 0;
|
|
491
|
-
for (const r of rows) {
|
|
492
|
-
if (r.status !== "run")
|
|
493
|
-
continue;
|
|
494
|
-
if (input.budgetUsd !== null && total + r.estimate_usd > input.budgetUsd + 1e-9) {
|
|
495
|
-
r.status = "over-budget";
|
|
496
|
-
over += r.estimate_usd;
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
499
|
-
total += r.estimate_usd;
|
|
500
|
-
}
|
|
501
|
-
const runnable = rows.filter((r) => r.status === "run").length;
|
|
502
|
-
return { ok: true, rows, runnable, total_usd: total, over_budget_usd: over, floor };
|
|
503
|
-
}
|
|
504
|
-
/** The unit owning each tree path under `cut` (NO_UNIT for none). */
|
|
505
|
-
export function unitOwner(cut, paths) {
|
|
506
|
-
const owner = new Map();
|
|
507
|
-
for (const [unit, ps] of assign(cut, paths).byUnit)
|
|
508
|
-
for (const p of ps)
|
|
509
|
-
owner.set(p, unit);
|
|
510
|
-
return (p) => owner.get(p) ?? NO_UNIT;
|
|
511
|
-
}
|
|
512
|
-
// ---- the prompt ----------------------------------------------------------------
|
|
513
|
-
/** The cases one row runs as: the row itself, or — a row over a file too
|
|
514
|
-
* big for one case — one per range, `<row>#<i>`, each the ranged file plus
|
|
515
|
-
* the paired tests. */
|
|
516
|
-
export function stage2Cases(row) {
|
|
517
|
-
if (row.ranges === undefined)
|
|
518
|
-
return [
|
|
519
|
-
{ id: row.id, part: 1, parts: 1, files: [...row.files, ...row.tests], lines: row.lines },
|
|
520
|
-
];
|
|
521
|
-
const code = row.files[0];
|
|
522
|
-
const tests = row.tests.reduce((n, t) => n + t.lines, 0);
|
|
523
|
-
return row.ranges.map((r, i) => ({
|
|
524
|
-
id: `${row.id}#${i + 1}`,
|
|
525
|
-
part: i + 1,
|
|
526
|
-
parts: row.ranges.length,
|
|
527
|
-
files: [{ ...code, lines: rangeCaseLines(r, 0), range: r }, ...row.tests],
|
|
528
|
-
lines: rangeCaseLines(r, tests),
|
|
529
|
-
}));
|
|
530
|
-
}
|
|
531
|
-
/** Stage-1 leads a stage-2 case carries at most. */
|
|
532
|
-
export const STAGE2_MAX_LEADS = 8;
|
|
533
|
-
/** A lead's reason is cut to this many characters. */
|
|
534
|
-
const LEAD_MAX_CHARS = 300;
|
|
535
|
-
/**
|
|
536
|
-
* The stage-1 suggestions a stage-2 row's cases carry: those naming the
|
|
537
|
-
* row's lens and at least one of its code files, newest first (by `at`), one
|
|
538
|
-
* per reason, at most `STAGE2_MAX_LEADS`. Each keeps only the row's files it
|
|
539
|
-
* names; its reason is flattened to one line and capped. The companion's row reason is one line; what stage 1 saw in these
|
|
540
|
-
* files is otherwise never in front of the lens. Pure.
|
|
541
|
-
*/
|
|
542
|
-
export function stage2Leads(row, suggestions) {
|
|
543
|
-
const mine = new Set(row.files.map((f) => f.path));
|
|
544
|
-
const seen = new Set();
|
|
545
|
-
const out = [];
|
|
546
|
-
const newest = [...suggestions].sort((a, b) => (b.at ?? "").localeCompare(a.at ?? ""));
|
|
547
|
-
for (const s of newest) {
|
|
548
|
-
if (s.lens !== row.lens)
|
|
549
|
-
continue;
|
|
550
|
-
const files = s.files.filter((f) => mine.has(f));
|
|
551
|
-
// Model-written: one line, capped, so a lead can never pass for a
|
|
552
|
-
// header or a findings block in the task string.
|
|
553
|
-
const flat = s.reason.replace(/\s+/g, " ").trim();
|
|
554
|
-
const reason = flat.length > LEAD_MAX_CHARS ? `${flat.slice(0, LEAD_MAX_CHARS - 1)}…` : flat;
|
|
555
|
-
if (files.length === 0 || reason === "" || seen.has(reason))
|
|
556
|
-
continue;
|
|
557
|
-
seen.add(reason);
|
|
558
|
-
out.push({ files, reason });
|
|
559
|
-
if (out.length >= STAGE2_MAX_LEADS)
|
|
560
|
-
break;
|
|
561
|
-
}
|
|
562
|
-
return out;
|
|
563
|
-
}
|
|
564
|
-
/** The stage-2 task string for one row — or, for a ranged row, for the one
|
|
565
|
-
* case whose `files` (from `stage2Cases`) are given. `leads` are what stage 1
|
|
566
|
-
* pointed this lens at in the row's files (`stage2Leads`). */
|
|
567
|
-
export function stage2CasePrompt(row, ctx, caseFiles, leads = []) {
|
|
568
|
-
const list = (fs) => fs.map((f) => focusLine(f, ctx.root)).join("\n");
|
|
569
|
-
const code = caseFiles ? caseFiles.slice(0, row.files.length) : row.files;
|
|
570
|
-
return [
|
|
571
|
-
`SCAN CASE ${ctx.caseId} — stage 2, ${row.id}: ${row.lens}${row.units.length > 1 ? ` across units ${row.units.join(", ")}` : ` in unit ${row.units[0]}`}.`,
|
|
572
|
-
`Scan sha: ${ctx.sha}`,
|
|
573
|
-
`Tree root (the repository at the scan sha, read-only): ${ctx.root}`,
|
|
574
|
-
TREE_PATHS_NOTE,
|
|
575
|
-
"",
|
|
576
|
-
`Why this case: ${row.reason}`,
|
|
577
|
-
...(row.floor
|
|
578
|
-
? [
|
|
579
|
-
leads.length > 0
|
|
580
|
-
? "This row is the deep scan's floor: every file is read with this lens whether or not a first look pointed here."
|
|
581
|
-
: "This row is the deep scan's floor: every file is read with this lens whether or not a first look pointed here, so nobody names a shape for you — find it in the focus files yourself.",
|
|
582
|
-
]
|
|
583
|
-
: []),
|
|
584
|
-
...(leads.length > 0
|
|
585
|
-
? [
|
|
586
|
-
"",
|
|
587
|
-
`What the first look pointed this lens at in these files (${leads.length} — leads to check first, not findings, and not the limit of your reading):`,
|
|
588
|
-
...leads.map((l) => `- ${l.files.map((f) => `\`${f}\``).join(", ")}: ${l.reason}`),
|
|
589
|
-
]
|
|
590
|
-
: []),
|
|
591
|
-
"",
|
|
592
|
-
`Focus files (${code.length} — read each in full; they are one case because the problem may live between them):`,
|
|
593
|
-
list(code),
|
|
594
|
-
...rangeNote(code),
|
|
595
|
-
...(row.tests.length > 0
|
|
596
|
-
? [
|
|
597
|
-
"",
|
|
598
|
-
`Their tests (${row.tests.length} — read them for the contract the code is held to; a missing test is not a finding):`,
|
|
599
|
-
list(row.tests),
|
|
600
|
-
]
|
|
601
|
-
: []),
|
|
602
|
-
"",
|
|
603
|
-
`Answer with the findings block headed \`FINDINGS ${row.lens} SCAN ${ctx.caseId} @${ctx.sha}\` (or NONE).`,
|
|
604
|
-
].join("\n");
|
|
605
|
-
}
|
|
606
|
-
/** The lines one range case reads: the range, and the paired tests that
|
|
607
|
-
* ride in every range case — the row's total, its price and its cases all
|
|
608
|
-
* count it this one way. */
|
|
609
|
-
function rangeCaseLines(r, pairedLines) {
|
|
610
|
-
return r.end - r.start + 1 + pairedLines;
|
|
611
|
-
}
|
|
612
|
-
/** The key a stage-2 start must quote: the runnable rows, the sha, the
|
|
613
|
-
* validator setting and the cap. */
|
|
614
|
-
export function stage2Key(parts) {
|
|
615
|
-
return planKey({ stage: 2, parts });
|
|
616
|
-
}
|
|
617
|
-
//# sourceMappingURL=stage2.js.map
|