@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21
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/CHANGELOG.md +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- package/engine/jev/jev-client.ts +0 -173
package/engine/cli/upgrade.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { readdir } from 'node:fs/promises'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import { setUpBoard } from './board-in-paseo'
|
|
5
1
|
import type { PaseoPlugins } from './computer-adapters'
|
|
6
|
-
import {
|
|
7
|
-
formatChecks,
|
|
8
|
-
hasProblem,
|
|
9
|
-
reason,
|
|
10
|
-
type DoctorCheck,
|
|
11
|
-
} from './doctor-checks'
|
|
2
|
+
import { reason, type DoctorCheck } from './doctor-checks'
|
|
12
3
|
import { describeRun, goingRuns } from './going-runs'
|
|
4
|
+
import type { ListProcesses } from './live-runs'
|
|
5
|
+
import { resumeCommand } from './run-modes'
|
|
6
|
+
import {
|
|
7
|
+
boardFingerprint,
|
|
8
|
+
finishUpgrade,
|
|
9
|
+
type UpgradeEnd,
|
|
10
|
+
} from './upgrade-finish'
|
|
13
11
|
|
|
14
12
|
import { LUCA_PACKAGE } from '../config/luca-version'
|
|
15
13
|
|
|
@@ -18,7 +16,11 @@ import { LUCA_PACKAGE } from '../config/luca-version'
|
|
|
18
16
|
* never under a going run.
|
|
19
17
|
*
|
|
20
18
|
* 1. Refuses while any run is going (limit waits and stuck runs included)
|
|
21
|
-
* and lists them by spec and repo.
|
|
19
|
+
* and lists them by spec and repo. A run is going when `ps` shows its
|
|
20
|
+
* engine, or its engine is gone but the board will restart it; when `ps`
|
|
21
|
+
* fails, every unfinished run is (see `goingRuns`). An unfinished run
|
|
22
|
+
* nothing runs (such as a crash) doesn't stop it: upgrade says how to
|
|
23
|
+
* resume it on the new version.
|
|
22
24
|
* 2. Picks the version: `to` exactly when given, older ones included (the
|
|
23
25
|
* way back from a broken version). Without it, the installed version's
|
|
24
26
|
* channel: an alpha install gets the `alpha` tag, any other gets
|
|
@@ -28,10 +30,20 @@ import { LUCA_PACKAGE } from '../config/luca-version'
|
|
|
28
30
|
* 4. Reloads the board in Paseo, keeping its settings, and rewrites its
|
|
29
31
|
* engine and Bun paths (see `setUpBoard`).
|
|
30
32
|
* 5. Says when `/reload-skills` is needed: when the board's files changed.
|
|
31
|
-
* 6.
|
|
33
|
+
* 6. Copies Luca's own skills (every one in the new install's `skills/`)
|
|
34
|
+
* into `~/.claude/skills` (see `installLucaSkills`).
|
|
35
|
+
* 7. Ends with doctor's computer checks (see `computerChecks`).
|
|
36
|
+
*
|
|
37
|
+
* Steps 4-7 (`finishUpgrade`) run in the new install, not in this process
|
|
38
|
+
* (#529): this process runs the old version's code, which doesn't know
|
|
39
|
+
* what the new version's board setup, skills, and checks are. So after
|
|
40
|
+
* step 3 upgrade hands off to the new install's `luca upgrade --finish`,
|
|
41
|
+
* passes its exit code through, and doesn't run them itself. A new install
|
|
42
|
+
* that can't finish (a version from before #529, such as a downgrade's)
|
|
43
|
+
* gets them from this process, as before.
|
|
32
44
|
*
|
|
33
|
-
* npm's registry, Bun, Paseo,
|
|
34
|
-
* tests use fakes.
|
|
45
|
+
* npm's registry, Bun, Paseo, `ps`, the new install, and the computer
|
|
46
|
+
* checks are adapters, so tests use fakes.
|
|
35
47
|
*/
|
|
36
48
|
|
|
37
49
|
/** npm's registry lookups for `@alecsibilia/luca`. */
|
|
@@ -46,8 +58,28 @@ export type BunGlobal = {
|
|
|
46
58
|
addGlobal: (args: { spec: string }) => Promise<void>
|
|
47
59
|
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
export type { UpgradeEnd }
|
|
62
|
+
|
|
63
|
+
/** How handing the last steps to the new install went. */
|
|
64
|
+
export type HandOff =
|
|
65
|
+
/** The new install ran them; it exited with `exit_code`. */
|
|
66
|
+
| { handed_off: true; exit_code: number }
|
|
67
|
+
/** The new install can't run them, and `why` (one sentence, no period). */
|
|
68
|
+
| { handed_off: false; why: string }
|
|
69
|
+
|
|
70
|
+
/** The newly installed Luca. */
|
|
71
|
+
export type NewInstall = {
|
|
72
|
+
/**
|
|
73
|
+
* Runs `luca upgrade --finish` in the new install of `version`, which
|
|
74
|
+
* prints its own lines; or says why it can't. Throws when the new
|
|
75
|
+
* install couldn't be started.
|
|
76
|
+
*/
|
|
77
|
+
finish: (args: {
|
|
78
|
+
version: string
|
|
79
|
+
/** The board folder's fingerprint before the install. */
|
|
80
|
+
board_before: string | null
|
|
81
|
+
}) => Promise<HandOff>
|
|
82
|
+
}
|
|
51
83
|
|
|
52
84
|
/** The first version of new Luca; without `--to`, nothing older is installed. */
|
|
53
85
|
const FIRST_NEW_MAJOR = 14
|
|
@@ -115,74 +147,61 @@ export const channelTarget = ({
|
|
|
115
147
|
return { ok: true, version }
|
|
116
148
|
}
|
|
117
149
|
|
|
118
|
-
/**
|
|
119
|
-
* A fingerprint of every file in `dir` but its `node_modules`, or `null`
|
|
120
|
-
* when it can't be read.
|
|
121
|
-
*/
|
|
122
|
-
const fingerprint = async (dir: string): Promise<string | null> => {
|
|
123
|
-
const lines: string[] = []
|
|
124
|
-
const walk = async (at: string): Promise<void> => {
|
|
125
|
-
const entries = await readdir(at, { withFileTypes: true })
|
|
126
|
-
for (const entry of entries) {
|
|
127
|
-
const path = join(at, entry.name)
|
|
128
|
-
if (entry.isDirectory()) {
|
|
129
|
-
if (entry.name !== 'node_modules') await walk(path)
|
|
130
|
-
} else if (entry.isFile()) {
|
|
131
|
-
const bytes = await Bun.file(path).arrayBuffer()
|
|
132
|
-
lines.push(`${path}\t${Bun.hash(bytes)}`)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
await walk(dir)
|
|
138
|
-
} catch {
|
|
139
|
-
return null
|
|
140
|
-
}
|
|
141
|
-
return lines.sort().join('\n')
|
|
142
|
-
}
|
|
143
|
-
|
|
144
150
|
/**
|
|
145
151
|
* Runs `luca upgrade`: refuses while a run is going, picks the version,
|
|
146
|
-
* installs it with Bun,
|
|
147
|
-
*
|
|
148
|
-
* a failed step, or a check that is a problem ends it
|
|
152
|
+
* installs it with Bun, then hands the last steps to `new_install` (see
|
|
153
|
+
* `finishUpgrade`), or runs them here when it can't take them. Never
|
|
154
|
+
* throws: a refusal, a failed step, or a check that is a problem ends it
|
|
155
|
+
* with `ok: false`.
|
|
149
156
|
*
|
|
150
157
|
* @example
|
|
151
|
-
* const end = await runUpgrade({ to: null, installed_version: lucaVersion(), runs_dir: defaultRunsDir(), registry_path, npm, bun, paseo, board_dir, engine_path, bun_path, computer_checks, log: console.log })
|
|
158
|
+
* const end = await runUpgrade({ to: null, home: homedir(), installed_version: lucaVersion(), runs_dir: defaultRunsDir(), registry_path, list_processes: listProcesses, npm, bun, paseo, board_dir, skills_dir, engine_path, bun_path, new_install, computer_checks, log: console.log })
|
|
152
159
|
*/
|
|
153
160
|
export const runUpgrade = async ({
|
|
154
161
|
to,
|
|
162
|
+
home,
|
|
155
163
|
installed_version,
|
|
156
164
|
runs_dir,
|
|
157
165
|
registry_path,
|
|
166
|
+
list_processes,
|
|
158
167
|
npm,
|
|
159
168
|
bun,
|
|
160
169
|
paseo,
|
|
161
170
|
board_dir,
|
|
171
|
+
skills_dir,
|
|
162
172
|
engine_path,
|
|
163
173
|
bun_path,
|
|
174
|
+
new_install,
|
|
164
175
|
computer_checks,
|
|
165
176
|
log,
|
|
166
177
|
}: {
|
|
167
178
|
/** `--to <version>`, or `null` to stay on the installed channel. */
|
|
168
179
|
to: string | null
|
|
180
|
+
/** The home folder, whose `.claude/skills` gets Luca's own skills. */
|
|
181
|
+
home: string
|
|
169
182
|
/** The version of Luca installed now. */
|
|
170
183
|
installed_version: string
|
|
171
184
|
runs_dir: string
|
|
172
185
|
/** The board's run registry. */
|
|
173
186
|
registry_path: string
|
|
187
|
+
/** Every running process's command line, to tell live engines from gone ones. */
|
|
188
|
+
list_processes: ListProcesses
|
|
174
189
|
npm: NpmRegistry
|
|
175
190
|
bun: BunGlobal
|
|
176
191
|
paseo: PaseoPlugins
|
|
177
192
|
/** The board folder inside Luca's install folder. */
|
|
178
193
|
board_dir: string
|
|
194
|
+
/** The skills folder inside Luca's install folder. */
|
|
195
|
+
skills_dir: string
|
|
179
196
|
/** The real path of the installed `luca-run`. */
|
|
180
197
|
engine_path: string
|
|
181
198
|
/** Bun's own path. */
|
|
182
199
|
bun_path: string
|
|
200
|
+
/** The install `bun add -g` makes, which finishes the upgrade. */
|
|
201
|
+
new_install: NewInstall
|
|
183
202
|
/**
|
|
184
203
|
* Doctor's computer checks of the new install, run after the board
|
|
185
|
-
* reload; none when not given.
|
|
204
|
+
* reload when this process runs the last steps; none when not given.
|
|
186
205
|
*/
|
|
187
206
|
computer_checks?: () => Promise<DoctorCheck[]>
|
|
188
207
|
log: (line: string) => void
|
|
@@ -192,17 +211,30 @@ export const runUpgrade = async ({
|
|
|
192
211
|
return { ok, message }
|
|
193
212
|
}
|
|
194
213
|
try {
|
|
195
|
-
const going = await goingRuns({
|
|
214
|
+
const going = await goingRuns({
|
|
215
|
+
runs_dir,
|
|
216
|
+
registry_path,
|
|
217
|
+
list_processes,
|
|
218
|
+
})
|
|
196
219
|
if (!going.ok) {
|
|
197
220
|
return say({ message: `Refused: ${going.error}`, ok: false })
|
|
198
221
|
}
|
|
199
222
|
if (going.runs.length > 0) {
|
|
223
|
+
const unchecked =
|
|
224
|
+
going.ps_error === null
|
|
225
|
+
? ''
|
|
226
|
+
: ` Luca couldn't list the processes (${going.ps_error}), so every unfinished run counts as going.`
|
|
200
227
|
return say({
|
|
201
|
-
message: `Refused: runs are going, so an upgrade would change their engine halfway
|
|
228
|
+
message: `Refused: runs are going, so an upgrade would change their engine halfway.${unchecked} Wait for them to end (or stop them), then try again:\n${going.runs.map(describeRun).join('\n')}`,
|
|
202
229
|
ok: false,
|
|
203
230
|
})
|
|
204
231
|
}
|
|
205
232
|
log('[luca upgrade] no run is going')
|
|
233
|
+
if (going.stopped.length > 0) {
|
|
234
|
+
log(
|
|
235
|
+
`[luca upgrade] These runs stopped before they ended, and nothing runs them now. They don't stop the upgrade. To go on with one on the new version, after the upgrade:\n${going.stopped.map((run) => `${describeRun(run)}: ${resumeCommand(run)}`).join('\n')}`
|
|
236
|
+
)
|
|
237
|
+
}
|
|
206
238
|
|
|
207
239
|
let version: string
|
|
208
240
|
if (to !== null) {
|
|
@@ -222,39 +254,48 @@ export const runUpgrade = async ({
|
|
|
222
254
|
})
|
|
223
255
|
}
|
|
224
256
|
|
|
225
|
-
const before = await
|
|
257
|
+
const before = await boardFingerprint(board_dir)
|
|
226
258
|
const spec = `${LUCA_PACKAGE}@${version}`
|
|
227
259
|
log(`[luca upgrade] installing ${spec} (from ${installed_version})`)
|
|
228
260
|
await bun.addGlobal({ spec })
|
|
229
261
|
log(`[luca upgrade] installed ${spec}`)
|
|
230
262
|
|
|
231
|
-
|
|
232
|
-
|
|
263
|
+
let hand_off: HandOff
|
|
264
|
+
try {
|
|
265
|
+
hand_off = await new_install.finish({
|
|
266
|
+
version,
|
|
267
|
+
board_before: before,
|
|
268
|
+
})
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return say({
|
|
271
|
+
message: `Luca ${version} is installed, but Luca couldn't start it to finish the upgrade (${reason(error)}). Run luca init to finish: it reloads the board and copies Luca's skills.`,
|
|
272
|
+
ok: false,
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
if (hand_off.handed_off) {
|
|
276
|
+
// The new install printed its own lines, its last one included.
|
|
277
|
+
return hand_off.exit_code === 0
|
|
278
|
+
? { ok: true, message: `Luca ${version} is installed.` }
|
|
279
|
+
: say({
|
|
280
|
+
message: `Luca ${version} is installed, but its last steps failed (exit ${hand_off.exit_code}). See above for what to fix.`,
|
|
281
|
+
ok: false,
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
log(
|
|
285
|
+
`[luca upgrade] ${hand_off.why}, so Luca ${installed_version} does the last steps.`
|
|
286
|
+
)
|
|
287
|
+
return await finishUpgrade({
|
|
288
|
+
version,
|
|
289
|
+
board_before: before,
|
|
290
|
+
home,
|
|
233
291
|
paseo,
|
|
234
|
-
// Upgrade only reloads; `luca init` asks to turn plugins on.
|
|
235
|
-
ask: async () => false,
|
|
236
292
|
board_dir,
|
|
293
|
+
skills_dir,
|
|
237
294
|
engine_path,
|
|
238
295
|
bun_path,
|
|
296
|
+
computer_checks,
|
|
239
297
|
log,
|
|
240
298
|
})
|
|
241
|
-
if (board.ok) {
|
|
242
|
-
const after = await fingerprint(board_dir)
|
|
243
|
-
if (before === null || after === null || before !== after) {
|
|
244
|
-
log(
|
|
245
|
-
"[luca upgrade] The board's files changed: run /reload-skills in a Paseo chat so its slash commands are up to date."
|
|
246
|
-
)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
const end = say({
|
|
250
|
-
message: board.ok
|
|
251
|
-
? `Luca ${version} is installed.`
|
|
252
|
-
: `Luca ${version} is installed, but the board wasn't reloaded. Run luca init to fix it.`,
|
|
253
|
-
ok: board.ok,
|
|
254
|
-
})
|
|
255
|
-
const checks = (await computer_checks?.()) ?? []
|
|
256
|
-
for (const line of formatChecks({ checks })) log(line)
|
|
257
|
-
return { ...end, ok: end.ok && !hasProblem({ checks }) }
|
|
258
299
|
} catch (error) {
|
|
259
300
|
// Such as a failed `bun add -g` or registry lookup.
|
|
260
301
|
return say({ message: reason(error), ok: false })
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads of single config fields, each with the default the engine uses when
|
|
3
|
+
* the config leaves it out: the prepare and test time limits, how many
|
|
4
|
+
* prepares run at once, the decision model, the models block, and the feature
|
|
5
|
+
* map's path.
|
|
6
|
+
*/
|
|
7
|
+
import { normalize } from 'node:path'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
DecisionModelConfigSchema,
|
|
11
|
+
ModelsConfigSchema,
|
|
12
|
+
type EngineConfig,
|
|
13
|
+
type ModelsConfig,
|
|
14
|
+
} from './engine-config-schema'
|
|
15
|
+
|
|
16
|
+
import type { DecisionModelId } from '../jev/jev-schemas'
|
|
17
|
+
import { DEFAULT_COMMAND_TIMEOUT_MS } from '../shell/run-command'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How long the prepare command may run when the config doesn't say: 30
|
|
21
|
+
* minutes. A first build in a fresh worktree can take many minutes
|
|
22
|
+
* (HeartGold's takes about 8), far past the 5 minutes other commands get.
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_PREPARE_TIMEOUT_MS = 30 * 60 * 1000
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The prepare command's time limit: the config's `prepare_timeout_ms`, else
|
|
28
|
+
* the default.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* prepareTimeoutOf({ config: { ...config, prepare_timeout_ms: 3_600_000 } }) // 3_600_000
|
|
32
|
+
*/
|
|
33
|
+
export const prepareTimeoutOf = ({
|
|
34
|
+
config,
|
|
35
|
+
}: {
|
|
36
|
+
config: EngineConfig
|
|
37
|
+
}): number => config.prepare_timeout_ms ?? DEFAULT_PREPARE_TIMEOUT_MS
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Each test command's time limit (#542): the config's `test_timeout_ms`,
|
|
41
|
+
* else the engine's default for any command. It holds for every run of a
|
|
42
|
+
* test command (baseline, red check, gates); types and lint keep the default.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* testTimeoutOf({ config: { ...config, test_timeout_ms: 900_000 } }) // 900_000
|
|
46
|
+
*/
|
|
47
|
+
export const testTimeoutOf = ({ config }: { config: EngineConfig }): number =>
|
|
48
|
+
config.test_timeout_ms ?? DEFAULT_COMMAND_TIMEOUT_MS
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* How many prepare runs may go at once when the config doesn't say: 1. A
|
|
52
|
+
* build is often heavy on the CPU, so builds at the same time only slow each
|
|
53
|
+
* other down (HeartGold's four first builds took 19 to 25 minutes each at
|
|
54
|
+
* once, against 8 to 10 alone).
|
|
55
|
+
*/
|
|
56
|
+
export const DEFAULT_PREPARE_CONCURRENCY = 1
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* How many prepare runs may go at once: the config's `prepare_concurrency`,
|
|
60
|
+
* else the default.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* prepareConcurrencyOf({ config: { ...config, prepare_concurrency: 2 } }) // 2
|
|
64
|
+
*/
|
|
65
|
+
export const prepareConcurrencyOf = ({
|
|
66
|
+
config,
|
|
67
|
+
}: {
|
|
68
|
+
config: EngineConfig
|
|
69
|
+
}): number => config.prepare_concurrency ?? DEFAULT_PREPARE_CONCURRENCY
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The decision model a run asks: the config's `decision_model.model`, else
|
|
73
|
+
* Clef (`DEFAULT_DECISION_MODEL`).
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* decisionModelOf({ config: { ...config, decision_model: { model: '@cf/cloudflare/clef-flash' } } })
|
|
77
|
+
* // '@cf/cloudflare/clef-flash'
|
|
78
|
+
*/
|
|
79
|
+
export const decisionModelOf = ({
|
|
80
|
+
config,
|
|
81
|
+
}: {
|
|
82
|
+
config: EngineConfig
|
|
83
|
+
}): DecisionModelId =>
|
|
84
|
+
config.decision_model?.model ?? DecisionModelConfigSchema.parse({}).model
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The run's model floors and confidence bar: the config's `models`, else
|
|
88
|
+
* the schema's defaults (every floor `opus`).
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* modelsOf({ config }).floors['test-writer'] // 'opus'
|
|
92
|
+
*/
|
|
93
|
+
export const modelsOf = ({ config }: { config: EngineConfig }): ModelsConfig =>
|
|
94
|
+
config.models ?? ModelsConfigSchema.parse({})
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The feature map's path (#583), in the form a worktree-relative path takes
|
|
98
|
+
* everywhere else (`normalize`, no leading `./`), or `null` with no map.
|
|
99
|
+
* `./docs/feature-map.md` and `docs//feature-map.md` are both
|
|
100
|
+
* `docs/feature-map.md`. The role rules, the permission rules, the prompts,
|
|
101
|
+
* and the red check all read the map's path here, so they agree on it
|
|
102
|
+
* whatever way the config spells it.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* featureMapPath({ config: { ...config, feature_map: './docs/feature-map.md' } }) // 'docs/feature-map.md'
|
|
106
|
+
*/
|
|
107
|
+
export const featureMapPath = ({
|
|
108
|
+
config,
|
|
109
|
+
}: {
|
|
110
|
+
config: EngineConfig
|
|
111
|
+
}): string | null =>
|
|
112
|
+
config.feature_map === undefined
|
|
113
|
+
? null
|
|
114
|
+
: normalize(config.feature_map).replace(/^\.\//, '')
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Whether a feature map path names a folder of pages (#597): any path not
|
|
118
|
+
* ending in `.md`. The path alone decides, so the guard and the prompts,
|
|
119
|
+
* which never read the disk, agree with the red check. `loadEngineConfig`
|
|
120
|
+
* refuses such a path when it is a file, so a map file must end in `.md`.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* isFeatureMapFolder({ path: 'docs/feature-map' }) // true
|
|
124
|
+
* isFeatureMapFolder({ path: 'docs/feature-map.md' }) // false
|
|
125
|
+
*/
|
|
126
|
+
export const isFeatureMapFolder = ({ path }: { path: string }): boolean =>
|
|
127
|
+
!path.toLowerCase().endsWith('.md')
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import isEqual from 'lodash/isEqual'
|
|
2
|
+
import omit from 'lodash/omit'
|
|
3
|
+
import reduce from 'lodash/reduce'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
import type { EngineConfig } from './engine-config'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The config fields a resumed run takes from the repo's
|
|
10
|
+
* `.luca/config.json` again (#516). They say how the engine builds a
|
|
11
|
+
* checkout before its tests, how long a test may run, or which new docs
|
|
12
|
+
* may be committed, not what the tests are, so a run can change them
|
|
13
|
+
* halfway without making its earlier results wrong: a repo that swaps a
|
|
14
|
+
* slow `prepare` for a fast cached one gets it on the next resume.
|
|
15
|
+
*
|
|
16
|
+
* - `prepare`: the command run before each test run in a checkout.
|
|
17
|
+
* - `prepare_timeout_ms`: how long that command may run.
|
|
18
|
+
* - `prepare_concurrency`: how many prepare runs may go at once. The
|
|
19
|
+
* prepare slots take the limit on each call (`prepareCheck`), so a new
|
|
20
|
+
* one counts from the next prepare run.
|
|
21
|
+
* - `test_concurrency`: how many test runs may go at once (#544). The test
|
|
22
|
+
* slots take the limit on each call (`inTestSlot`), as the prepare slots
|
|
23
|
+
* do, so a new one counts from the next test run.
|
|
24
|
+
* - `test_timeout_ms`: how long each test command may run (#542). Each run
|
|
25
|
+
* of a test command reads it then, so a new one counts from the next.
|
|
26
|
+
* - `test_case_timeout_ms`: how long one fast test may take (#580). Each run
|
|
27
|
+
* of a fast test command reads it then, so a new one counts from the next.
|
|
28
|
+
* - `leftover_allow`: more new markdown the leftover scan lets through
|
|
29
|
+
* (#520). Each scan reads it then, so a run stuck on a leftover can be
|
|
30
|
+
* unstuck by allowing the file and resuming.
|
|
31
|
+
*/
|
|
32
|
+
export const RELOADED_FIELDS = [
|
|
33
|
+
'prepare',
|
|
34
|
+
'prepare_timeout_ms',
|
|
35
|
+
'prepare_concurrency',
|
|
36
|
+
'test_concurrency',
|
|
37
|
+
'test_timeout_ms',
|
|
38
|
+
'test_case_timeout_ms',
|
|
39
|
+
'leftover_allow',
|
|
40
|
+
] as const
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The config fields a run keeps from its `run_started`, whatever the file
|
|
44
|
+
* says on a resume:
|
|
45
|
+
*
|
|
46
|
+
* - `checks`: the baseline, the red check, and the gates compare test
|
|
47
|
+
* results across the run. A ticket takes another's baseline by commit
|
|
48
|
+
* alone (`sharedBaselineStep` in `core/decide-build.ts`), and the red
|
|
49
|
+
* check holds every old passing test of the baseline to the new run by
|
|
50
|
+
* file and name (`checkRed` in `gates/red-check.ts`). A baseline taken
|
|
51
|
+
* with old commands is not comparable with a run of new ones. Intake also
|
|
52
|
+
* judged the run's tickets against these commands (a `bun` test command
|
|
53
|
+
* for the red check).
|
|
54
|
+
* - `test_file_patterns`: which files are tests decides the red check, the
|
|
55
|
+
* role rules, and which commits were the test-writer's.
|
|
56
|
+
* - `test_helper_patterns`: which non-test files the test-writer may write
|
|
57
|
+
* (#619), as `test_file_patterns` says which test files it may.
|
|
58
|
+
* - `test_setup_files`: part of what the tests are, as `checks` is.
|
|
59
|
+
* - `feature_map`: says which test files belong to which feature (#583), as
|
|
60
|
+
* `test_file_patterns` says which files are tests: the red check holds a
|
|
61
|
+
* ticket's new test files to it, and the role rules let the test-writer
|
|
62
|
+
* edit it.
|
|
63
|
+
* - `rule_files`: the rules lens judges against them; tickets already
|
|
64
|
+
* reviewed were judged on the old ones.
|
|
65
|
+
* - `muninn`: the run's memory vault is fixed at start (`run_started.memory`).
|
|
66
|
+
* - `run_budget_tokens`: the budget the run's tokens are counted against,
|
|
67
|
+
* and the board's, from `run_started`; a raise is a `retry` reply.
|
|
68
|
+
* - `decision_model`: the model shadow mode asks (#534), so a run's asks
|
|
69
|
+
* are all of one model and can be scored together.
|
|
70
|
+
* - `models`: the model floors and the confidence bar (#603), so one run's
|
|
71
|
+
* routing stays consistent.
|
|
72
|
+
* - `generators`: the commands writers may ask the engine to run (#618),
|
|
73
|
+
* whose output is committed with the tickets, as the prompts named them.
|
|
74
|
+
*/
|
|
75
|
+
export const FROZEN_FIELDS = [
|
|
76
|
+
'checks',
|
|
77
|
+
'test_file_patterns',
|
|
78
|
+
'test_helper_patterns',
|
|
79
|
+
'test_setup_files',
|
|
80
|
+
'feature_map',
|
|
81
|
+
'rule_files',
|
|
82
|
+
'muninn',
|
|
83
|
+
'run_budget_tokens',
|
|
84
|
+
'decision_model',
|
|
85
|
+
'models',
|
|
86
|
+
'generators',
|
|
87
|
+
] as const
|
|
88
|
+
|
|
89
|
+
const positiveInt = z.number().int().positive().nullable()
|
|
90
|
+
const patterns = z.array(z.string().min(1)).nullable()
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* One reloaded field that changed: its value in the run before (`from`)
|
|
94
|
+
* and in the file now (`to`). `null` is a field left out, so a field added
|
|
95
|
+
* or removed is a change too.
|
|
96
|
+
*/
|
|
97
|
+
export const ConfigChangeSchema = z.discriminatedUnion('field', [
|
|
98
|
+
z.object({
|
|
99
|
+
field: z.literal('prepare'),
|
|
100
|
+
from: z.string().min(1).nullable(),
|
|
101
|
+
to: z.string().min(1).nullable(),
|
|
102
|
+
}),
|
|
103
|
+
z.object({
|
|
104
|
+
field: z.literal('prepare_timeout_ms'),
|
|
105
|
+
from: positiveInt,
|
|
106
|
+
to: positiveInt,
|
|
107
|
+
}),
|
|
108
|
+
z.object({
|
|
109
|
+
field: z.literal('prepare_concurrency'),
|
|
110
|
+
from: positiveInt,
|
|
111
|
+
to: positiveInt,
|
|
112
|
+
}),
|
|
113
|
+
z.object({
|
|
114
|
+
field: z.literal('test_concurrency'),
|
|
115
|
+
from: positiveInt,
|
|
116
|
+
to: positiveInt,
|
|
117
|
+
}),
|
|
118
|
+
z.object({
|
|
119
|
+
field: z.literal('test_timeout_ms'),
|
|
120
|
+
from: positiveInt,
|
|
121
|
+
to: positiveInt,
|
|
122
|
+
}),
|
|
123
|
+
z.object({
|
|
124
|
+
field: z.literal('test_case_timeout_ms'),
|
|
125
|
+
from: positiveInt,
|
|
126
|
+
to: positiveInt,
|
|
127
|
+
}),
|
|
128
|
+
z.object({
|
|
129
|
+
field: z.literal('leftover_allow'),
|
|
130
|
+
from: patterns,
|
|
131
|
+
to: patterns,
|
|
132
|
+
}),
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
export type ConfigChange = z.infer<typeof ConfigChangeSchema>
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The reloaded fields whose value differs between the run's config
|
|
139
|
+
* (`current`) and the one just read (`next`), old to new, in
|
|
140
|
+
* `RELOADED_FIELDS` order. Frozen fields are never changes. Pure.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* configChanges({ current: { ...config, prepare: 'make' }, next: { ...config, prepare: 'make fast' } })
|
|
144
|
+
* // [{ field: 'prepare', from: 'make', to: 'make fast' }]
|
|
145
|
+
*/
|
|
146
|
+
export const configChanges = ({
|
|
147
|
+
current,
|
|
148
|
+
next,
|
|
149
|
+
}: {
|
|
150
|
+
current: EngineConfig
|
|
151
|
+
next: EngineConfig
|
|
152
|
+
}): ConfigChange[] => {
|
|
153
|
+
const all: ConfigChange[] = [
|
|
154
|
+
{
|
|
155
|
+
field: 'prepare',
|
|
156
|
+
from: current.prepare ?? null,
|
|
157
|
+
to: next.prepare ?? null,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
field: 'prepare_timeout_ms',
|
|
161
|
+
from: current.prepare_timeout_ms ?? null,
|
|
162
|
+
to: next.prepare_timeout_ms ?? null,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
field: 'prepare_concurrency',
|
|
166
|
+
from: current.prepare_concurrency ?? null,
|
|
167
|
+
to: next.prepare_concurrency ?? null,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
field: 'test_concurrency',
|
|
171
|
+
from: current.test_concurrency ?? null,
|
|
172
|
+
to: next.test_concurrency ?? null,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
field: 'test_timeout_ms',
|
|
176
|
+
from: current.test_timeout_ms ?? null,
|
|
177
|
+
to: next.test_timeout_ms ?? null,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
field: 'test_case_timeout_ms',
|
|
181
|
+
from: current.test_case_timeout_ms ?? null,
|
|
182
|
+
to: next.test_case_timeout_ms ?? null,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
field: 'leftover_allow',
|
|
186
|
+
from: current.leftover_allow ?? null,
|
|
187
|
+
to: next.leftover_allow ?? null,
|
|
188
|
+
},
|
|
189
|
+
]
|
|
190
|
+
return all.filter(({ from, to }) => !isEqual(from, to))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const applyChange = ({
|
|
194
|
+
config,
|
|
195
|
+
change,
|
|
196
|
+
}: {
|
|
197
|
+
config: EngineConfig
|
|
198
|
+
change: ConfigChange
|
|
199
|
+
}): EngineConfig => {
|
|
200
|
+
switch (change.field) {
|
|
201
|
+
case 'prepare':
|
|
202
|
+
return change.to === null
|
|
203
|
+
? omit(config, 'prepare')
|
|
204
|
+
: { ...config, prepare: change.to }
|
|
205
|
+
case 'prepare_timeout_ms':
|
|
206
|
+
return change.to === null
|
|
207
|
+
? omit(config, 'prepare_timeout_ms')
|
|
208
|
+
: { ...config, prepare_timeout_ms: change.to }
|
|
209
|
+
case 'prepare_concurrency':
|
|
210
|
+
return change.to === null
|
|
211
|
+
? omit(config, 'prepare_concurrency')
|
|
212
|
+
: { ...config, prepare_concurrency: change.to }
|
|
213
|
+
case 'test_concurrency':
|
|
214
|
+
return change.to === null
|
|
215
|
+
? omit(config, 'test_concurrency')
|
|
216
|
+
: { ...config, test_concurrency: change.to }
|
|
217
|
+
case 'test_timeout_ms':
|
|
218
|
+
return change.to === null
|
|
219
|
+
? omit(config, 'test_timeout_ms')
|
|
220
|
+
: { ...config, test_timeout_ms: change.to }
|
|
221
|
+
case 'test_case_timeout_ms':
|
|
222
|
+
return change.to === null
|
|
223
|
+
? omit(config, 'test_case_timeout_ms')
|
|
224
|
+
: { ...config, test_case_timeout_ms: change.to }
|
|
225
|
+
case 'leftover_allow':
|
|
226
|
+
return change.to === null
|
|
227
|
+
? omit(config, 'leftover_allow')
|
|
228
|
+
: { ...config, leftover_allow: change.to }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `config` with each change's new value: set, or left out for `null`. Pure.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* withConfigChanges({ config, changes: [{ field: 'prepare', from: 'make', to: null }] })
|
|
237
|
+
* // config without prepare
|
|
238
|
+
*/
|
|
239
|
+
export const withConfigChanges = ({
|
|
240
|
+
config,
|
|
241
|
+
changes,
|
|
242
|
+
}: {
|
|
243
|
+
config: EngineConfig
|
|
244
|
+
changes: ConfigChange[]
|
|
245
|
+
}): EngineConfig =>
|
|
246
|
+
reduce(
|
|
247
|
+
changes,
|
|
248
|
+
(next, change) => applyChange({ config: next, change }),
|
|
249
|
+
config
|
|
250
|
+
)
|