@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/src/setup/engine.ts
CHANGED
|
@@ -19,23 +19,14 @@
|
|
|
19
19
|
// trackersink.ts's. The GitHub reader port is declared and injected, but no
|
|
20
20
|
// engine behaviour calls it yet (the PR replay builds its own reader).
|
|
21
21
|
//
|
|
22
|
-
// Persistence: `<state home>/setup/unit-cut.json
|
|
23
|
-
// the
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// unreadable file is reported and refuses every write: it may hold the
|
|
28
|
-
// operator's saved cut, and silently starting fresh over it would lose that.
|
|
22
|
+
// Persistence: the unit cut is unitcutstore.ts's, `<state home>/setup/unit-cut.json`:
|
|
23
|
+
// the saved cut (the one survey and scan reuse), the previous saved cut (so a
|
|
24
|
+
// re-cut can be compared and undone), and at most one draft. The ports the
|
|
25
|
+
// scan shares (the checkout, the state home, the case runner) are
|
|
26
|
+
// scanports.ts's.
|
|
29
27
|
import { isSampleable } from "./survey.js";
|
|
30
28
|
import type { MemoryStore } from "../memory/store.js";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
repoFacts,
|
|
34
|
-
unitCandidates,
|
|
35
|
-
vendoredRoots,
|
|
36
|
-
type RepoFacts,
|
|
37
|
-
type UnitCandidate,
|
|
38
|
-
} from "./facts.js";
|
|
29
|
+
import { repoFacts, type RepoFacts } from "./facts.js";
|
|
39
30
|
import {
|
|
40
31
|
SurveyRunner,
|
|
41
32
|
type SurveyInputs,
|
|
@@ -79,96 +70,49 @@ import {
|
|
|
79
70
|
import type { ProfileCheck } from "./profile.js";
|
|
80
71
|
import {
|
|
81
72
|
DEFAULT_CASE_BUDGET,
|
|
82
|
-
applyOps,
|
|
83
73
|
assign,
|
|
84
|
-
checkCut,
|
|
85
|
-
normalizeCut,
|
|
86
|
-
splitUnit,
|
|
87
74
|
type CaseBudget,
|
|
88
75
|
type CasePlan,
|
|
89
76
|
type UnitCut,
|
|
90
77
|
} from "./unitcut.js";
|
|
78
|
+
import type {
|
|
79
|
+
CaseRunner,
|
|
80
|
+
Refusal,
|
|
81
|
+
RepoReader,
|
|
82
|
+
StateFs,
|
|
83
|
+
} from "@quickchatai/pi-quickreview/src/scan/scanports.js";
|
|
84
|
+
import {
|
|
85
|
+
UnitCutStore,
|
|
86
|
+
candidateCut,
|
|
87
|
+
type CutStatus,
|
|
88
|
+
type CutView,
|
|
89
|
+
type SavedCut,
|
|
90
|
+
type UnitCutFile,
|
|
91
|
+
} from "@quickchatai/pi-quickreview/src/scan/unitcutstore.js";
|
|
91
92
|
|
|
92
93
|
// ---- ports -------------------------------------------------------------------
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
export
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
* Paths are relative to that root; the implementation refuses any that would
|
|
116
|
-
* leave it. The same shape roots the config home (`SetupPorts.config`). */
|
|
117
|
-
export interface StateFs {
|
|
118
|
-
readonly location: string;
|
|
119
|
-
/** The file's text, or null when it does not exist. Throws only when it
|
|
120
|
-
* exists and cannot be read — the caller reports that, never overwrites. */
|
|
121
|
-
read(rel: string): string | null;
|
|
122
|
-
/** tmp+rename atomic write; creates parent directories. */
|
|
123
|
-
write(rel: string, text: string): void;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** One case: a unit (or part of one) handed to background pi subagents inside
|
|
127
|
-
* the companion. */
|
|
128
|
-
export interface CaseSpec {
|
|
129
|
-
id: string;
|
|
130
|
-
unit: string;
|
|
131
|
-
agent: string;
|
|
132
|
-
files: string[];
|
|
133
|
-
prompt: string;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** How a case ended: the lens's final answer and what it cost (null when
|
|
137
|
-
* the runner could not tell), or why there is none. A failed or aborted case
|
|
138
|
-
* may still have spent money: `cost_usd` on a failure is what it spent before
|
|
139
|
-
* it ended (0 when it never started, null or absent when the runner cannot
|
|
140
|
-
* tell) — a caller holding a budget counts it. */
|
|
141
|
-
export type CaseOutcome =
|
|
142
|
-
| {
|
|
143
|
-
ok: true;
|
|
144
|
-
output: string;
|
|
145
|
-
cost_usd: number | null;
|
|
146
|
-
/** The files the case's file-read tool calls opened (`read`, and
|
|
147
|
-
* `grep` given a `path`), in call order, each resolved to the absolute
|
|
148
|
-
* path the call opened (against the case's working directory, which is
|
|
149
|
-
* not the scanned tree) — `[]` when it read nothing. Absent when the
|
|
150
|
-
* runner cannot tell, which is unknown, never "read nothing". The scan
|
|
151
|
-
* judges a lens that read none of its focus files as no reading at all. */
|
|
152
|
-
reads?: string[];
|
|
153
|
-
/** Where the runner kept the case's event stream, relative to its
|
|
154
|
-
* transcript directory; absent when it kept none. */
|
|
155
|
-
transcript?: string;
|
|
156
|
-
}
|
|
157
|
-
| { ok: false; reason: string; cost_usd?: number | null; transcript?: string };
|
|
158
|
-
|
|
159
|
-
/** Runs cases as background subagents (survey, scan). The
|
|
160
|
-
* engine decides what runs when and how many at once; the runner runs one
|
|
161
|
-
* case to its end. Resolves for every outcome — a failed or aborted case is
|
|
162
|
-
* `{ok:false}`, never a rejection. `onCost`, when given, hears the case's
|
|
163
|
-
* running cost each time the runner learns more of it, so a caller with a
|
|
164
|
-
* hard cap can abort a case mid-flight rather than after the fact. */
|
|
165
|
-
export interface CaseRunner {
|
|
166
|
-
run(
|
|
167
|
-
spec: CaseSpec,
|
|
168
|
-
signal: AbortSignal,
|
|
169
|
-
onCost?: (spentSoFar: number) => void,
|
|
170
|
-
): Promise<CaseOutcome>;
|
|
171
|
-
}
|
|
95
|
+
// The checkout, the state home and the case runner: the scan's own ports,
|
|
96
|
+
// shared with it (scanports.ts).
|
|
97
|
+
export type {
|
|
98
|
+
CaseOutcome,
|
|
99
|
+
CaseRunner,
|
|
100
|
+
CaseSpec,
|
|
101
|
+
Refusal,
|
|
102
|
+
RepoReader,
|
|
103
|
+
StateFs,
|
|
104
|
+
} from "@quickchatai/pi-quickreview/src/scan/scanports.js";
|
|
105
|
+
// The stored unit cut (unitcutstore.ts), under the names setup has always used.
|
|
106
|
+
export {
|
|
107
|
+
UNIT_CUT_FILE,
|
|
108
|
+
UNIT_CUT_VERSION,
|
|
109
|
+
type CutStatus,
|
|
110
|
+
type CutView,
|
|
111
|
+
type DraftCut,
|
|
112
|
+
type SavedCut,
|
|
113
|
+
type UnitCutFile,
|
|
114
|
+
type UnitSummary,
|
|
115
|
+
} from "@quickchatai/pi-quickreview/src/scan/unitcutstore.js";
|
|
172
116
|
|
|
173
117
|
export interface TrackerTicket {
|
|
174
118
|
title: string;
|
|
@@ -241,124 +185,24 @@ export interface SetupOptions {
|
|
|
241
185
|
surveyConcurrency?: number;
|
|
242
186
|
}
|
|
243
187
|
|
|
244
|
-
// ---- the persisted file ------------------------------------------------------
|
|
245
|
-
|
|
246
|
-
export const UNIT_CUT_FILE = "setup/unit-cut.json";
|
|
247
|
-
export const UNIT_CUT_VERSION = 1;
|
|
248
|
-
|
|
249
|
-
export interface SavedCut extends UnitCut {
|
|
250
|
-
saved_at: string;
|
|
251
|
-
/** The commit the cut was checked against, when readable. */
|
|
252
|
-
head: string | null;
|
|
253
|
-
/** How many files the cut covered at save — a later listing that differs is
|
|
254
|
-
* how a stale cut shows itself. */
|
|
255
|
-
files: number;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export interface DraftCut extends UnitCut {
|
|
259
|
-
updated_at: string;
|
|
260
|
-
/** `candidates` = the deterministic guess taken as-is; `companion` = a
|
|
261
|
-
* proposal the companion wrote; `adjusted` = either, after operator edits. */
|
|
262
|
-
source: "candidates" | "companion" | "adjusted";
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export interface UnitCutFile {
|
|
266
|
-
version: number;
|
|
267
|
-
saved: SavedCut | null;
|
|
268
|
-
previous: SavedCut | null;
|
|
269
|
-
draft: DraftCut | null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
188
|
// ---- results -----------------------------------------------------------------
|
|
273
189
|
|
|
274
|
-
export type Refusal = { ok: false; errors: string[] };
|
|
275
|
-
|
|
276
|
-
export interface UnitSummary {
|
|
277
|
-
name: string;
|
|
278
|
-
kind: string;
|
|
279
|
-
roots: string[];
|
|
280
|
-
note: string;
|
|
281
|
-
files: number | null;
|
|
282
|
-
/** Files a pass would actually read (lockfiles, binaries, prose skipped). */
|
|
283
|
-
reviewable: number | null;
|
|
284
|
-
lines: number | null;
|
|
285
|
-
/** One per case; a unit that fits is one case under its own name. */
|
|
286
|
-
cases: Array<{ id: string; files: number; lines: number; over_budget: boolean }> | null;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export interface CutView {
|
|
290
|
-
units: UnitSummary[];
|
|
291
|
-
ignored: string[];
|
|
292
|
-
ignored_files: number | null;
|
|
293
|
-
unassigned: string[] | null;
|
|
294
|
-
/** Structural refusals — should never appear on a stored cut; shown if a
|
|
295
|
-
* hand-edited file carries one. */
|
|
296
|
-
errors: string[];
|
|
297
|
-
/** What save would refuse for (a draft may carry these; a saved cut that
|
|
298
|
-
* shows one has gone stale against the repo). */
|
|
299
|
-
blocking_save: string[];
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export interface CutStatus {
|
|
303
|
-
ok: boolean;
|
|
304
|
-
/** Why the file or the repo could not be read — the rest is then partial. */
|
|
305
|
-
problems: string[];
|
|
306
|
-
/** The cut file exists but could not be read: `saved`/`draft` are then
|
|
307
|
-
* unknown, not absent. */
|
|
308
|
-
cut_file_unreadable: boolean;
|
|
309
|
-
saved: (CutView & { saved_at: string; head: string | null; files_at_save: number }) | null;
|
|
310
|
-
draft: (CutView & { updated_at: string; source: DraftCut["source"] }) | null;
|
|
311
|
-
has_previous: boolean;
|
|
312
|
-
repo_files: number | null;
|
|
313
|
-
head: string | null;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
190
|
export type FactsResult = { ok: true; head: string | null; facts: RepoFacts } | Refusal;
|
|
317
191
|
|
|
318
|
-
/** Why a stored cut is not one this version can read, or null when it is (or
|
|
319
|
-
* is absent). Structure only — the partition itself is `checkCut`'s. */
|
|
320
|
-
function cutShapeError(cut: unknown): string | null {
|
|
321
|
-
if (cut === null) return null;
|
|
322
|
-
if (typeof cut !== "object") return "is not an object";
|
|
323
|
-
const c = cut as { units?: unknown; ignored?: unknown };
|
|
324
|
-
const strings = (v: unknown): boolean =>
|
|
325
|
-
Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
326
|
-
if (!Array.isArray(c.units)) return "has no `units` array";
|
|
327
|
-
for (const [i, u] of c.units.entries()) {
|
|
328
|
-
const unit = (u ?? {}) as { name?: unknown; roots?: unknown; kind?: unknown; note?: unknown };
|
|
329
|
-
if (
|
|
330
|
-
typeof unit.name !== "string" ||
|
|
331
|
-
!strings(unit.roots) ||
|
|
332
|
-
typeof unit.kind !== "string" ||
|
|
333
|
-
typeof unit.note !== "string"
|
|
334
|
-
) {
|
|
335
|
-
return `units[${i}] is not {name, roots, kind, note}`;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
if (!strings(c.ignored)) return "has no `ignored` array of paths";
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
192
|
// ---- engine ------------------------------------------------------------------
|
|
343
193
|
|
|
344
|
-
/** The cut setup offers when none is saved: the candidates as units, the
|
|
345
|
-
* vendored roots ignored. */
|
|
346
|
-
function candidateCut(candidates: readonly UnitCandidate[], files: readonly string[]): UnitCut {
|
|
347
|
-
return {
|
|
348
|
-
units: candidates.map((c) => ({ name: c.name, roots: c.roots, kind: c.kind, note: c.reason })),
|
|
349
|
-
ignored: vendoredRoots(files),
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
194
|
export class SetupEngine {
|
|
354
195
|
private readonly budget: CaseBudget;
|
|
355
196
|
private readonly surveyRun: SurveyRunner;
|
|
197
|
+
/** The unit cut: its file, its views and its three verbs. */
|
|
198
|
+
private readonly cut: UnitCutStore;
|
|
356
199
|
|
|
357
200
|
constructor(
|
|
358
201
|
private readonly ports: SetupPorts,
|
|
359
202
|
opts: SetupOptions = {},
|
|
360
203
|
) {
|
|
361
204
|
this.budget = opts.caseBudget ?? DEFAULT_CASE_BUDGET;
|
|
205
|
+
this.cut = new UnitCutStore(ports, this.budget);
|
|
362
206
|
this.surveyRun = new SurveyRunner(ports, () => this.surveyInputs(), {
|
|
363
207
|
repoRoot: ports.repo.location,
|
|
364
208
|
...(opts.surveyConcurrency !== undefined ? { concurrency: opts.surveyConcurrency } : {}),
|
|
@@ -366,17 +210,11 @@ export class SetupEngine {
|
|
|
366
210
|
}
|
|
367
211
|
|
|
368
212
|
private files(): string[] | null {
|
|
369
|
-
|
|
370
|
-
return listed === null ? null : [...listed].sort();
|
|
213
|
+
return this.cut.files();
|
|
371
214
|
}
|
|
372
215
|
|
|
373
216
|
private noRepo(): Refusal {
|
|
374
|
-
return
|
|
375
|
-
ok: false,
|
|
376
|
-
errors: [
|
|
377
|
-
`no readable checkout at ${this.ports.repo.location} — is the reviewed repo mounted?`,
|
|
378
|
-
],
|
|
379
|
-
};
|
|
217
|
+
return this.cut.noRepo();
|
|
380
218
|
}
|
|
381
219
|
|
|
382
220
|
/** The deterministic repo facts. Recomputed on every call — they are cheap
|
|
@@ -391,325 +229,53 @@ export class SetupEngine {
|
|
|
391
229
|
};
|
|
392
230
|
}
|
|
393
231
|
|
|
394
|
-
// -- the
|
|
232
|
+
// -- the unit cut (unitcutstore.ts) --
|
|
395
233
|
|
|
396
234
|
private load(): { file: UnitCutFile; error: string | null } {
|
|
397
|
-
|
|
398
|
-
version: UNIT_CUT_VERSION,
|
|
399
|
-
saved: null,
|
|
400
|
-
previous: null,
|
|
401
|
-
draft: null,
|
|
402
|
-
};
|
|
403
|
-
let text: string | null;
|
|
404
|
-
try {
|
|
405
|
-
text = this.ports.state.read(UNIT_CUT_FILE);
|
|
406
|
-
} catch (err) {
|
|
407
|
-
return {
|
|
408
|
-
file: empty,
|
|
409
|
-
error: `${UNIT_CUT_FILE} in ${this.ports.state.location} cannot be read: ${String(err)}`,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
if (text === null) return { file: empty, error: null };
|
|
413
|
-
try {
|
|
414
|
-
const raw = JSON.parse(text) as Partial<UnitCutFile>;
|
|
415
|
-
if (typeof raw !== "object" || raw === null || raw.version !== UNIT_CUT_VERSION) {
|
|
416
|
-
throw new Error(
|
|
417
|
-
`unknown version ${JSON.stringify((raw as { version?: unknown } | null)?.version)}`,
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
const file: UnitCutFile = {
|
|
421
|
-
version: UNIT_CUT_VERSION,
|
|
422
|
-
saved: raw.saved ?? null,
|
|
423
|
-
previous: raw.previous ?? null,
|
|
424
|
-
draft: raw.draft ?? null,
|
|
425
|
-
};
|
|
426
|
-
// A hand-edited file must be reported, never crash a view: every later
|
|
427
|
-
// reader (assign, splitUnit) takes the shape on trust.
|
|
428
|
-
for (const key of ["saved", "previous", "draft"] as const) {
|
|
429
|
-
const bad = cutShapeError(file[key]);
|
|
430
|
-
if (bad !== null) throw new Error(`\`${key}\` ${bad}`);
|
|
431
|
-
}
|
|
432
|
-
return { file, error: null };
|
|
433
|
-
} catch (err) {
|
|
434
|
-
return {
|
|
435
|
-
file: empty,
|
|
436
|
-
error:
|
|
437
|
-
`${UNIT_CUT_FILE} in ${this.ports.state.location} is not a unit cut this version reads (${String(err)}) — ` +
|
|
438
|
-
"nothing will overwrite it; move it aside to start over",
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
private store(file: UnitCutFile): void {
|
|
444
|
-
this.ports.state.write(UNIT_CUT_FILE, `${JSON.stringify(file, null, 2)}\n`);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/** Load for a write: an unreadable file refuses rather than being replaced. */
|
|
448
|
-
private loadForWrite(): { file: UnitCutFile } | Refusal {
|
|
449
|
-
const { file, error } = this.load();
|
|
450
|
-
return error === null ? { file } : { ok: false, errors: [error] };
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// -- views --
|
|
454
|
-
|
|
455
|
-
/** A unit's case plans over its reviewable (not sweep-skipped) files. */
|
|
456
|
-
private unitCases(
|
|
457
|
-
name: string,
|
|
458
|
-
owned: readonly string[],
|
|
459
|
-
lines: (p: string) => number,
|
|
460
|
-
): CasePlan[] {
|
|
461
|
-
return splitUnit(
|
|
462
|
-
name,
|
|
463
|
-
owned.filter((f) => !isSweepSkipped(f)),
|
|
464
|
-
lines,
|
|
465
|
-
this.budget,
|
|
466
|
-
);
|
|
235
|
+
return this.cut.load();
|
|
467
236
|
}
|
|
468
237
|
|
|
469
238
|
private view(cut: UnitCut, files: readonly string[], lines: (p: string) => number): CutView {
|
|
470
|
-
|
|
471
|
-
// A cut with structural errors (a hand-edited file) still gets a
|
|
472
|
-
// best-effort view, with the errors beside it.
|
|
473
|
-
const a = assign(cut, files);
|
|
474
|
-
const sizeErrors: string[] = [];
|
|
475
|
-
const units: UnitSummary[] = cut.units.map((u) => {
|
|
476
|
-
const owned = a.byUnit.get(u.name) ?? [];
|
|
477
|
-
const reviewable = owned.filter((f) => !isSweepSkipped(f));
|
|
478
|
-
let cases: CasePlan[] = [];
|
|
479
|
-
let unavailable = false;
|
|
480
|
-
try {
|
|
481
|
-
cases = this.unitCases(u.name, owned, lines);
|
|
482
|
-
} catch (err) {
|
|
483
|
-
unavailable = true;
|
|
484
|
-
sizeErrors.push(String(err));
|
|
485
|
-
}
|
|
486
|
-
return {
|
|
487
|
-
name: u.name,
|
|
488
|
-
kind: u.kind,
|
|
489
|
-
roots: u.roots,
|
|
490
|
-
note: u.note,
|
|
491
|
-
files: owned.length,
|
|
492
|
-
reviewable: reviewable.length,
|
|
493
|
-
lines: unavailable ? null : cases.reduce((n, c) => n + c.lines, 0),
|
|
494
|
-
cases: unavailable
|
|
495
|
-
? null
|
|
496
|
-
: cases.map((c) => ({
|
|
497
|
-
id: c.id,
|
|
498
|
-
files: c.files.length,
|
|
499
|
-
lines: c.lines,
|
|
500
|
-
over_budget: c.over_budget,
|
|
501
|
-
})),
|
|
502
|
-
};
|
|
503
|
-
});
|
|
504
|
-
return {
|
|
505
|
-
units,
|
|
506
|
-
ignored: cut.ignored,
|
|
507
|
-
ignored_files: a.ignored.length,
|
|
508
|
-
unassigned: a.unassigned,
|
|
509
|
-
errors: [...errors, ...sizeErrors],
|
|
510
|
-
blocking_save,
|
|
511
|
-
};
|
|
239
|
+
return this.cut.view(cut, files, lines);
|
|
512
240
|
}
|
|
513
241
|
|
|
514
242
|
private lineCounter(): (p: string) => number {
|
|
515
|
-
|
|
516
|
-
return (p) => {
|
|
517
|
-
let n = cache.get(p);
|
|
518
|
-
if (n === undefined) {
|
|
519
|
-
const measured = this.ports.repo.lines(p);
|
|
520
|
-
if (measured === null)
|
|
521
|
-
throw new Error(
|
|
522
|
-
`line count UNKNOWN for ${p} — unreadable, binary or too large; no case or price can be inferred`,
|
|
523
|
-
);
|
|
524
|
-
n = measured;
|
|
525
|
-
cache.set(p, n);
|
|
526
|
-
}
|
|
527
|
-
return n;
|
|
528
|
-
};
|
|
243
|
+
return this.cut.lineCounter();
|
|
529
244
|
}
|
|
530
245
|
|
|
531
246
|
/** The saved cut and the draft, each re-checked against the repo as it is
|
|
532
247
|
* now — so a saved cut the repo has outgrown (a new top-level directory no
|
|
533
248
|
* unit claims) shows up here instead of being silently skipped later. */
|
|
534
249
|
status(): CutStatus {
|
|
535
|
-
|
|
536
|
-
const problems = error === null ? [] : [error];
|
|
537
|
-
const files = this.files();
|
|
538
|
-
if (files === null) problems.push(this.noRepo().errors[0]!);
|
|
539
|
-
const head = files === null ? null : this.ports.repo.head();
|
|
540
|
-
const lines = this.lineCounter();
|
|
541
|
-
const see = (cut: UnitCut): CutView =>
|
|
542
|
-
files === null
|
|
543
|
-
? {
|
|
544
|
-
units: cut.units.map((u) => ({
|
|
545
|
-
...u,
|
|
546
|
-
files: null,
|
|
547
|
-
reviewable: null,
|
|
548
|
-
lines: null,
|
|
549
|
-
cases: null,
|
|
550
|
-
})),
|
|
551
|
-
ignored: cut.ignored,
|
|
552
|
-
ignored_files: null,
|
|
553
|
-
unassigned: null,
|
|
554
|
-
errors: [],
|
|
555
|
-
blocking_save: [],
|
|
556
|
-
}
|
|
557
|
-
: this.view(cut, files, lines);
|
|
558
|
-
return {
|
|
559
|
-
ok: problems.length === 0,
|
|
560
|
-
problems,
|
|
561
|
-
cut_file_unreadable: error !== null,
|
|
562
|
-
saved: file.saved
|
|
563
|
-
? {
|
|
564
|
-
...see(file.saved),
|
|
565
|
-
saved_at: file.saved.saved_at,
|
|
566
|
-
head: file.saved.head,
|
|
567
|
-
files_at_save: file.saved.files,
|
|
568
|
-
}
|
|
569
|
-
: null,
|
|
570
|
-
draft: file.draft
|
|
571
|
-
? { ...see(file.draft), updated_at: file.draft.updated_at, source: file.draft.source }
|
|
572
|
-
: null,
|
|
573
|
-
has_previous: file.previous !== null,
|
|
574
|
-
repo_files: files?.length ?? null,
|
|
575
|
-
head,
|
|
576
|
-
};
|
|
250
|
+
return this.cut.status();
|
|
577
251
|
}
|
|
578
252
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* Store a proposal as the draft, replacing any earlier draft (never the
|
|
583
|
-
* saved cut). `raw` is the companion's proposal — `{units, ignored?}` — or
|
|
584
|
-
* undefined to take the deterministic candidates as they are (vendored
|
|
585
|
-
* trees ignored). Refused when it is structurally wrong; save-blocking
|
|
586
|
-
* conditions are allowed in a draft and reported.
|
|
587
|
-
*/
|
|
253
|
+
/** Store a proposal as the draft (unitcutstore.ts `propose`). */
|
|
588
254
|
propose(raw?: unknown): { ok: true; draft: CutView } | Refusal {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
let cut: UnitCut;
|
|
594
|
-
let source: DraftCut["source"];
|
|
595
|
-
if (raw === undefined || raw === null || (typeof raw === "object" && !("units" in raw))) {
|
|
596
|
-
cut = candidateCut(unitCandidates(files), files);
|
|
597
|
-
source = "candidates";
|
|
598
|
-
} else {
|
|
599
|
-
const n = normalizeCut(raw);
|
|
600
|
-
if (!n.ok) return n;
|
|
601
|
-
cut = n.cut;
|
|
602
|
-
source = "companion";
|
|
603
|
-
}
|
|
604
|
-
const problems = checkCut(cut, files);
|
|
605
|
-
if (problems.errors.length > 0) return { ok: false, errors: problems.errors };
|
|
606
|
-
loaded.file.draft = { ...cut, updated_at: this.ports.clock().toISOString(), source };
|
|
607
|
-
this.store(loaded.file);
|
|
608
|
-
return { ok: true, draft: this.view(cut, files, this.lineCounter()) };
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* Apply the operator's corrections to the draft — starting one from the
|
|
613
|
-
* saved cut when there is no draft — all-or-nothing: a refused op, or a
|
|
614
|
-
* result that is structurally wrong, leaves the stored draft as it was.
|
|
615
|
-
*/
|
|
255
|
+
return this.cut.propose(raw);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Apply the operator's corrections to the draft (unitcutstore.ts `adjust`). */
|
|
616
259
|
adjust(ops: unknown): { ok: true; draft: CutView } | Refusal {
|
|
617
|
-
|
|
618
|
-
return { ok: false, errors: ["`ops` must be a non-empty array of adjust operations"] };
|
|
619
|
-
}
|
|
620
|
-
const files = this.files();
|
|
621
|
-
if (files === null) return this.noRepo();
|
|
622
|
-
const loaded = this.loadForWrite();
|
|
623
|
-
if ("ok" in loaded) return loaded;
|
|
624
|
-
const base = loaded.file.draft ?? loaded.file.saved;
|
|
625
|
-
if (!base)
|
|
626
|
-
return { ok: false, errors: ["there is no unit cut to adjust yet — propose one first"] };
|
|
627
|
-
const applied = applyOps({ units: base.units, ignored: base.ignored }, ops);
|
|
628
|
-
if (!applied.ok) return applied;
|
|
629
|
-
const problems = checkCut(applied.cut, files);
|
|
630
|
-
if (problems.errors.length > 0) return { ok: false, errors: problems.errors };
|
|
631
|
-
loaded.file.draft = {
|
|
632
|
-
...applied.cut,
|
|
633
|
-
updated_at: this.ports.clock().toISOString(),
|
|
634
|
-
source: "adjusted",
|
|
635
|
-
};
|
|
636
|
-
this.store(loaded.file);
|
|
637
|
-
return { ok: true, draft: this.view(applied.cut, files, this.lineCounter()) };
|
|
260
|
+
return this.cut.adjust(ops);
|
|
638
261
|
}
|
|
639
262
|
|
|
640
|
-
/**
|
|
641
|
-
* Promote the draft to the unit cut. Refused unless it is a complete
|
|
642
|
-
* partition of the repo as it is now — every file in exactly one unit or
|
|
643
|
-
* deliberately ignored, no unit left empty. The cut it replaces is kept as
|
|
644
|
-
* `previous`.
|
|
645
|
-
*/
|
|
263
|
+
/** Promote the draft to the unit cut (unitcutstore.ts `save`). */
|
|
646
264
|
save(): { ok: true; saved: CutView } | Refusal {
|
|
647
|
-
|
|
648
|
-
if (files === null) return this.noRepo();
|
|
649
|
-
const loaded = this.loadForWrite();
|
|
650
|
-
if ("ok" in loaded) return loaded;
|
|
651
|
-
const draft = loaded.file.draft;
|
|
652
|
-
if (!draft) {
|
|
653
|
-
return {
|
|
654
|
-
ok: false,
|
|
655
|
-
errors: [
|
|
656
|
-
loaded.file.saved
|
|
657
|
-
? "no draft to save — the saved cut is already in use; adjust it to start a draft"
|
|
658
|
-
: "no draft to save — propose a unit cut first",
|
|
659
|
-
],
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
const cut: UnitCut = { units: draft.units, ignored: draft.ignored };
|
|
663
|
-
const problems = checkCut(cut, files);
|
|
664
|
-
if (problems.errors.length > 0 || problems.blocking_save.length > 0) {
|
|
665
|
-
return { ok: false, errors: [...problems.errors, ...problems.blocking_save] };
|
|
666
|
-
}
|
|
667
|
-
loaded.file.previous = loaded.file.saved;
|
|
668
|
-
loaded.file.saved = {
|
|
669
|
-
...cut,
|
|
670
|
-
saved_at: this.ports.clock().toISOString(),
|
|
671
|
-
head: this.ports.repo.head(),
|
|
672
|
-
files: files.length,
|
|
673
|
-
};
|
|
674
|
-
loaded.file.draft = null;
|
|
675
|
-
this.store(loaded.file);
|
|
676
|
-
return { ok: true, saved: this.view(cut, files, this.lineCounter()) };
|
|
265
|
+
return this.cut.save();
|
|
677
266
|
}
|
|
678
267
|
|
|
679
|
-
// -- reuse --
|
|
680
|
-
|
|
681
268
|
/** The saved cut, for every later survey and scan; null when none is saved
|
|
682
269
|
* (or the file cannot be read — `status()` says which). */
|
|
683
270
|
savedCut(): SavedCut | null {
|
|
684
|
-
return this.
|
|
271
|
+
return this.cut.savedCut();
|
|
685
272
|
}
|
|
686
273
|
|
|
687
|
-
/**
|
|
688
|
-
* The saved cut as cases: every unit's reviewable files (optionally only
|
|
689
|
-
* those `include` keeps — a scan's stage 1 drops tests and migrations),
|
|
690
|
-
* split to fit the case budget, each case under its unit's name. Refused
|
|
691
|
-
* when there is no saved cut: a survey or scan never runs on a guess.
|
|
692
|
-
*/
|
|
274
|
+
/** The saved cut as cases (unitcutstore.ts `cases`). */
|
|
693
275
|
cases(
|
|
694
276
|
include: (path: string) => boolean = () => true,
|
|
695
277
|
): { ok: true; cases: CasePlan[] } | Refusal {
|
|
696
|
-
|
|
697
|
-
if (files === null) return this.noRepo();
|
|
698
|
-
const { file, error } = this.load();
|
|
699
|
-
if (error !== null) return { ok: false, errors: [error] };
|
|
700
|
-
if (!file.saved)
|
|
701
|
-
return { ok: false, errors: ["no saved unit cut — propose, adjust and save one first"] };
|
|
702
|
-
const a = assign(file.saved, files);
|
|
703
|
-
const lines = this.lineCounter();
|
|
704
|
-
const cases: CasePlan[] = [];
|
|
705
|
-
try {
|
|
706
|
-
for (const u of file.saved.units) {
|
|
707
|
-
cases.push(...this.unitCases(u.name, (a.byUnit.get(u.name) ?? []).filter(include), lines));
|
|
708
|
-
}
|
|
709
|
-
} catch (err) {
|
|
710
|
-
return { ok: false, errors: [String(err)] };
|
|
711
|
-
}
|
|
712
|
-
return { ok: true, cases };
|
|
278
|
+
return this.cut.cases(include);
|
|
713
279
|
}
|
|
714
280
|
|
|
715
281
|
// -- review history (src/setup/history.ts) --
|