@vzn/vx 0.0.651 → 0.0.653
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 +3 -4
- package/launcher.sh +2 -1
- package/package.json +5 -5
- package/schemas/error.json +1 -1
- package/schemas/show.json +15 -20
- package/skills/vx/SKILL.md +3 -3
- package/src/bin.ts +37 -3
- package/src/cache/archive.ts +197 -32
- package/src/cache/cache.ts +882 -230
- package/src/cache/checkpointer.ts +102 -0
- package/src/cache/file-hashes.ts +2 -1
- package/src/cache/fold-memo.ts +24 -4
- package/src/cache/fs-clock.ts +26 -0
- package/src/cache/git-inputs.ts +188 -22
- package/src/cache/history-stream.ts +151 -0
- package/src/cache/history-writer.ts +76 -0
- package/src/cache/index.ts +12 -1
- package/src/cache/inputs.ts +616 -135
- package/src/cache/key-fold.ts +1 -1
- package/src/cache/layer.ts +29 -16
- package/src/cache/layered-cache.ts +28 -7
- package/src/cache/lockfile-memo.ts +2 -1
- package/src/cache/output-index.ts +106 -44
- package/src/cache/plugin-cache.ts +2 -2
- package/src/cache/policy.ts +6 -2
- package/src/cache/remote-lane.ts +3 -1
- package/src/cache/run-history.ts +94 -25
- package/src/cache/schema.ts +46 -10
- package/src/cli/cache.ts +149 -36
- package/src/cli/completions.ts +2 -2
- package/src/cli/core-alias.ts +9 -2
- package/src/cli/docs.ts +4 -2
- package/src/cli/help.ts +27 -4
- package/src/cli/index.ts +62 -7
- package/src/cli/info.ts +5 -4
- package/src/cli/init.ts +55 -20
- package/src/cli/last.ts +218 -43
- package/src/cli/lock.ts +187 -24
- package/src/cli/plan-format.ts +24 -21
- package/src/cli/plugin-commands.ts +6 -1
- package/src/cli/plugin-templates.ts +36 -45
- package/src/cli/run.ts +68 -34
- package/src/cli/select.ts +160 -64
- package/src/cli/show.ts +70 -28
- package/src/cli/task-verb.ts +12 -7
- package/src/cli/upgrade.ts +210 -76
- package/src/cli/watch-filter.ts +88 -15
- package/src/cli/watch-fs.ts +34 -47
- package/src/cli/watch-judge.ts +73 -9
- package/src/cli/watch-set.ts +10 -8
- package/src/cli/watch.ts +190 -33
- package/src/cli/why.ts +107 -33
- package/src/cli/workspace-config.ts +11 -1
- package/src/config.ts +56 -30
- package/src/exec/env.ts +3 -1
- package/src/exec/executor.ts +116 -16
- package/src/exec/index.ts +1 -0
- package/src/exec/kill-tree.ts +7 -7
- package/src/exec/runner.ts +270 -108
- package/src/exec/sandbox-binds.ts +109 -18
- package/src/exec/sandbox-paths.ts +3 -1
- package/src/exec/sandbox-runtime.ts +446 -64
- package/src/exec/sandbox-violations.ts +250 -46
- package/src/exec/spawn-off.ts +10 -5
- package/src/exec/task-proxy.ts +93 -0
- package/src/exec/warm-shell.ts +29 -7
- package/src/graph/definition.ts +16 -3
- package/src/graph/folds.ts +89 -16
- package/src/graph/index.ts +8 -0
- package/src/graph/priorities.ts +8 -5
- package/src/graph/scheduler.ts +176 -39
- package/src/graph/stand-ins.ts +36 -0
- package/src/graph/task-graph.ts +347 -160
- package/src/graph/task-status.ts +5 -1
- package/src/handoff.ts +45 -10
- package/src/history/failure-mode.ts +39 -6
- package/src/history/history.ts +103 -30
- package/src/history/index.ts +2 -0
- package/src/history/metrics.ts +193 -73
- package/src/index.ts +6 -3
- package/src/orchestrator/admission.ts +14 -10
- package/src/orchestrator/affected-tasks.ts +35 -13
- package/src/orchestrator/command-windows.ts +8 -5
- package/src/orchestrator/deferred-outputs.ts +42 -13
- package/src/orchestrator/doctor.ts +21 -9
- package/src/orchestrator/download-policy.ts +8 -10
- package/src/orchestrator/excluded-keys.ts +15 -3
- package/src/orchestrator/execute-args.ts +9 -2
- package/src/orchestrator/execute-task.ts +612 -138
- package/src/orchestrator/graph-memo.ts +26 -4
- package/src/orchestrator/graph-stage.ts +27 -4
- package/src/orchestrator/held-servers.ts +36 -14
- package/src/orchestrator/hit-restore.ts +141 -93
- package/src/orchestrator/index.ts +4 -1
- package/src/orchestrator/keyed-projects.ts +53 -0
- package/src/orchestrator/local-floor.ts +21 -3
- package/src/orchestrator/local-shortcircuit.ts +2 -2
- package/src/orchestrator/lockfile-claim.ts +30 -4
- package/src/orchestrator/miss-save.ts +60 -61
- package/src/orchestrator/options.ts +13 -0
- package/src/orchestrator/output-host.ts +16 -4
- package/src/orchestrator/output-log.ts +1 -54
- package/src/orchestrator/persistent-task.ts +32 -16
- package/src/orchestrator/persistent.ts +152 -15
- package/src/orchestrator/placement.ts +82 -16
- package/src/orchestrator/plan.ts +39 -17
- package/src/orchestrator/plugin-host.ts +238 -110
- package/src/orchestrator/plugin.ts +59 -27
- package/src/orchestrator/prepare.ts +194 -38
- package/src/orchestrator/projects.ts +89 -10
- package/src/orchestrator/read-only.ts +115 -0
- package/src/orchestrator/remote-prefetch.ts +1 -1
- package/src/orchestrator/run-context.ts +6 -2
- package/src/orchestrator/run-failures.ts +101 -23
- package/src/orchestrator/run-id.ts +13 -2
- package/src/orchestrator/run-keys.ts +87 -28
- package/src/orchestrator/run-live.ts +16 -13
- package/src/orchestrator/run-lock.ts +27 -4
- package/src/orchestrator/run-records.ts +95 -74
- package/src/orchestrator/run-report.ts +3 -2
- package/src/orchestrator/run-stop.ts +17 -9
- package/src/orchestrator/run.ts +531 -122
- package/src/orchestrator/sandbox-fix.ts +40 -12
- package/src/orchestrator/sandbox-request.ts +314 -44
- package/src/orchestrator/save-lane.ts +24 -13
- package/src/orchestrator/shell-verdict.ts +29 -1
- package/src/orchestrator/signals.ts +103 -8
- package/src/orchestrator/stable-keys.ts +34 -27
- package/src/orchestrator/task-artifacts.ts +16 -3
- package/src/orchestrator/task-hash.ts +191 -34
- package/src/orchestrator/telemetry-host.ts +25 -6
- package/src/orchestrator/telemetry.ts +85 -13
- package/src/orchestrator/upstream.ts +3 -1
- package/src/report/events.ts +23 -6
- package/src/report/forecast.ts +103 -37
- package/src/report/framed-output.ts +179 -162
- package/src/report/index.ts +2 -1
- package/src/report/logger.ts +108 -31
- package/src/report/plain-output.ts +15 -9
- package/src/report/status-line.ts +28 -55
- package/src/report/summary.ts +61 -18
- package/src/settings.ts +50 -14
- package/src/ui/components.ts +36 -42
- package/src/ui/index.ts +3 -1
- package/src/ui/live-region.ts +67 -8
- package/src/ui/terminal.ts +12 -0
- package/src/ui/theme.ts +16 -1
- package/src/util/config-frame.ts +142 -6
- package/src/util/edit-distance.ts +17 -1
- package/src/util/errors.ts +23 -7
- package/src/util/foreign-cache-dir.ts +60 -0
- package/src/util/git-ignored.ts +59 -5
- package/src/util/index.ts +15 -1
- package/src/util/paths.ts +51 -7
- package/src/util/plain-text.ts +19 -0
- package/src/util/secret-mask.ts +57 -1
- package/src/util/settle.ts +18 -1
- package/src/util/size.ts +12 -2
- package/src/util/tail.ts +4 -1
- package/src/util/write-file.ts +86 -9
- package/src/workspace/affected.ts +225 -27
- package/src/workspace/bracket-literal.ts +2 -2
- package/src/workspace/config-bytecode.ts +23 -4
- package/src/workspace/config-eval.ts +90 -16
- package/src/workspace/config-imports.ts +96 -41
- package/src/workspace/config-schema.ts +192 -86
- package/src/workspace/dependencies-lock.ts +7 -14
- package/src/workspace/filter.ts +18 -2
- package/src/workspace/index.ts +3 -1
- package/src/workspace/json-data.ts +34 -8
- package/src/workspace/lockfile.ts +86 -37
- package/src/workspace/migration.ts +58 -31
- package/src/workspace/module-state.ts +62 -0
- package/src/workspace/nested-dirs.ts +8 -0
- package/src/workspace/project-loader.ts +276 -32
- package/src/workspace/repo-id.ts +29 -6
- package/src/workspace/workspace.ts +190 -30
package/src/graph/definition.ts
CHANGED
|
@@ -15,18 +15,31 @@ const memo = new WeakMap<TaskNode, string | undefined>()
|
|
|
15
15
|
export function taskDefinition(node: TaskNode): string | undefined {
|
|
16
16
|
if (memo.has(node)) return memo.get(node)
|
|
17
17
|
const exec = node.config.exec
|
|
18
|
+
const define = exec?.env?.define
|
|
18
19
|
const def =
|
|
19
20
|
exec?.command === undefined
|
|
20
21
|
? undefined
|
|
21
22
|
: xxh3hex(
|
|
22
|
-
`${node.id}\0${exec.command}\0${
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
`${node.id}\0${exec.command}\0${
|
|
24
|
+
// `[]` is what the stringify gives for no values: most tasks
|
|
25
|
+
// have none, and stringifying it for each was ~9 ms of an
|
|
26
|
+
// 8,002-hit warm run.
|
|
27
|
+
define === undefined || isEmpty(define)
|
|
28
|
+
? '[]'
|
|
29
|
+
: JSON.stringify(
|
|
30
|
+
Object.entries(define).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
|
|
31
|
+
)
|
|
32
|
+
}`,
|
|
25
33
|
)
|
|
26
34
|
memo.set(node, def)
|
|
27
35
|
return def
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
function isEmpty(o: object): boolean {
|
|
39
|
+
for (const _ in o) return false
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
/** Each task's definition (`taskDefinition`) by id, groups left out: what a history reader matches rows to. */
|
|
31
44
|
export function taskDefinitions(nodes: Iterable<TaskNode>): Map<string, string> {
|
|
32
45
|
const out = new Map<string, string>()
|
package/src/graph/folds.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
parseDependencySpec,
|
|
11
11
|
type DependencySpec,
|
|
12
12
|
} from '../util/index.js'
|
|
13
|
+
import { standInsOf, type StandIns } from './stand-ins.js'
|
|
13
14
|
import type { TaskNode } from './task-graph.js'
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -36,9 +37,13 @@ export function keyedDeps(node: TaskNode): readonly string[] {
|
|
|
36
37
|
* A filter selects among the task's own dependencies, never through a
|
|
37
38
|
* group: `dependsOn: ['build']` over a group `build` of `build.a` and
|
|
38
39
|
* `build.b` offers the filter `build`, not its members. A pattern that
|
|
39
|
-
* matches
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* matches a member (`build.*`) the filter folds neither directly nor
|
|
41
|
+
* through a selected group therefore left it out, and the key stopped
|
|
42
|
+
* moving with that member's inputs: a stale hit under a green run. That
|
|
43
|
+
* held when the pattern also matched another dependency directly
|
|
44
|
+
* (`dependsOn: ['build', 'build.a']`), so such a spec is refused, once,
|
|
45
|
+
* over the graph every key site reads, unless it matches the group
|
|
46
|
+
* itself (a negation of the group is a decision). One that matches
|
|
42
47
|
* nothing anywhere stays legal (a preset's pattern needn't match in every
|
|
43
48
|
* project). An exact name is refused earlier, at load, unless `dependsOn`
|
|
44
49
|
* names it (`assertFilterNamesDeclaredDeps`).
|
|
@@ -49,20 +54,37 @@ export function refuseFiltersThroughGroups(nodes: ReadonlyMap<string, TaskNode>)
|
|
|
49
54
|
if (filter === undefined || filter.length === 0) continue
|
|
50
55
|
const deps = keyedDeps(node).flatMap((id) => nodes.get(id) ?? [])
|
|
51
56
|
const groups = deps.filter(isGroupTask)
|
|
57
|
+
const folded = new Set<TaskNode>()
|
|
58
|
+
if (groups.length > 0) {
|
|
59
|
+
const units = deps.map((n) => ({ node: n, unit: n.id }))
|
|
60
|
+
const picked = selectFoldedDeps(units, filter, node.projectName, node.id, standInsOf(node))
|
|
61
|
+
for (const { node: n } of picked) {
|
|
62
|
+
folded.add(n)
|
|
63
|
+
if (isGroupTask(n)) for (const m of groupMembers(n, nodes)) folded.add(m)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
52
66
|
for (const raw of filter) {
|
|
53
67
|
const spec = parseSpec(raw, node.id)
|
|
54
68
|
if (spec.negated) continue
|
|
55
69
|
const matches = specMatcher(spec)
|
|
56
70
|
const hit = (n: TaskNode): boolean => matches(n, n.projectName === node.projectName)
|
|
57
|
-
|
|
71
|
+
const direct = deps.some(hit)
|
|
58
72
|
for (const group of groups) {
|
|
59
|
-
|
|
73
|
+
if (hit(group)) continue
|
|
74
|
+
const member = groupMembers(group, nodes).find(
|
|
75
|
+
(m) => hit(m) && !folded.has(m) && !deps.includes(m),
|
|
76
|
+
)
|
|
60
77
|
if (member === undefined) continue
|
|
78
|
+
const outcome = direct ? 'so the key leaves it out' : 'so it selects nothing'
|
|
61
79
|
throw new UserError(
|
|
62
|
-
`${node.id}: inputs.tasks: '${raw}' matches ${member.id} only through the group ${group.id},
|
|
80
|
+
`${node.id}: inputs.tasks: '${raw}' matches ${member.id} only through the group ${group.id}, ${outcome}; name '${group.taskName}' or depend on ${member.id} directly`,
|
|
63
81
|
)
|
|
64
82
|
}
|
|
65
|
-
if (
|
|
83
|
+
if (direct) continue
|
|
84
|
+
if (
|
|
85
|
+
namesNoDependency(spec, node.config.dependsOn ?? []) ||
|
|
86
|
+
namesTransitiveDependency(spec, node, nodes, hit)
|
|
87
|
+
) {
|
|
66
88
|
const named = deps.map((d) => d.id).join(', ') || 'none'
|
|
67
89
|
throw new UserError(
|
|
68
90
|
`${node.id}: inputs.tasks: '${raw}' names none of its dependencies (${named}), so it selects nothing and the key would not move with them; fix the name, or use [] to decouple on purpose`,
|
|
@@ -100,6 +122,34 @@ function namesNoDependency(spec: DependencySpec, dependsOn: readonly string[]):
|
|
|
100
122
|
})
|
|
101
123
|
}
|
|
102
124
|
|
|
125
|
+
/**
|
|
126
|
+
* An exact `pkg#task` filter that matched no dependency but names a task the
|
|
127
|
+
* key reaches only through one. `namesNoDependency` lets it through when a
|
|
128
|
+
* `^` entry names the task (the preset whose project lacks that dependency),
|
|
129
|
+
* but `a → b → c` with `dependsOn: ['^build']` and `['c#build']` selected
|
|
130
|
+
* none of a's direct dependencies, and an edit to c replayed a's hit.
|
|
131
|
+
*/
|
|
132
|
+
function namesTransitiveDependency(
|
|
133
|
+
spec: DependencySpec,
|
|
134
|
+
node: TaskNode,
|
|
135
|
+
nodes: ReadonlyMap<string, TaskNode>,
|
|
136
|
+
hit: (n: TaskNode) => boolean,
|
|
137
|
+
): boolean {
|
|
138
|
+
if (spec.kind !== 'cross' || isTaskPattern(spec.project) || isTaskPattern(spec.task)) return false
|
|
139
|
+
const seen = new Set<string>([node.id])
|
|
140
|
+
const stack = [...keyedDeps(node)]
|
|
141
|
+
while (stack.length > 0) {
|
|
142
|
+
const id = stack.pop()!
|
|
143
|
+
if (seen.has(id)) continue
|
|
144
|
+
seen.add(id)
|
|
145
|
+
const n = nodes.get(id)
|
|
146
|
+
if (n === undefined) continue
|
|
147
|
+
if (hit(n)) return true
|
|
148
|
+
stack.push(...keyedDeps(n))
|
|
149
|
+
}
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
103
153
|
function parseSpec(raw: string, selfTaskId: string): DependencySpec {
|
|
104
154
|
try {
|
|
105
155
|
return parseDependencySpec(raw, 'inputs.tasks')
|
|
@@ -162,16 +212,26 @@ export interface FoldCandidate {
|
|
|
162
212
|
* - `filter === undefined` → every candidate, as given.
|
|
163
213
|
* - `filter === []` → none (fully decoupled).
|
|
164
214
|
*
|
|
165
|
-
* Otherwise deduped by `unit`, first candidate kept. The unit is
|
|
166
|
-
* fold's own identity, not the task's: two groups over the same members
|
|
167
|
-
* share one hash
|
|
168
|
-
*
|
|
215
|
+
* Otherwise deduped by `unit`, first selected candidate kept. The unit is
|
|
216
|
+
* the fold's own identity, not the task's: two groups over the same members
|
|
217
|
+
* share one hash. A negation drops only the candidates it names, and the
|
|
218
|
+
* dedup runs after every entry, so a unit still selected through another
|
|
219
|
+
* candidate stays folded: deleting by unit let `['^*', '!c#build']` drop
|
|
220
|
+
* b#build's group with c#build's, and a change beneath both replayed the
|
|
221
|
+
* old hit.
|
|
222
|
+
*
|
|
223
|
+
* `standIns` is the task's `standInsOf`: an edge added past a
|
|
224
|
+
* default build on a package cycle is selected whenever an edge it went
|
|
225
|
+
* past is. That build folds nothing of its cycle, so `['build']` on
|
|
226
|
+
* `a ↔ b` selected a#build alone, dropped b#build, and an edit to b
|
|
227
|
+
* replayed the old hit.
|
|
169
228
|
*/
|
|
170
229
|
export function selectFoldedDeps(
|
|
171
230
|
deps: readonly FoldCandidate[],
|
|
172
231
|
filter: readonly string[] | undefined,
|
|
173
232
|
selfProjectName: string,
|
|
174
233
|
selfTaskId: string,
|
|
234
|
+
standIns?: StandIns,
|
|
175
235
|
): FoldCandidate[] {
|
|
176
236
|
if (filter === undefined) return [...deps]
|
|
177
237
|
|
|
@@ -180,18 +240,25 @@ export function selectFoldedDeps(
|
|
|
180
240
|
// Per-spec predicate, compiled once (exact compares + patterns).
|
|
181
241
|
const matchers = specs.map((spec) => specMatcher(spec))
|
|
182
242
|
|
|
183
|
-
const selected = new
|
|
243
|
+
const selected = new Set<FoldCandidate>()
|
|
184
244
|
for (let i = 0; i < specs.length; i++) {
|
|
185
245
|
const spec = specs[i]!
|
|
186
246
|
const matches = matchers[i]!
|
|
187
247
|
for (const d of deps) {
|
|
188
248
|
const isSelf = d.node.projectName === selfProjectName
|
|
189
249
|
if (!matches(d.node, isSelf)) continue
|
|
190
|
-
if (spec.negated) selected.delete(d
|
|
191
|
-
else
|
|
250
|
+
if (spec.negated) selected.delete(d)
|
|
251
|
+
else selected.add(d)
|
|
192
252
|
}
|
|
193
253
|
}
|
|
194
|
-
|
|
254
|
+
if (standIns !== undefined) {
|
|
255
|
+
const ids = new Set<string>()
|
|
256
|
+
for (const d of selected) ids.add(d.node.id)
|
|
257
|
+
for (const d of deps) if (standIns[d.node.id]?.some((p) => ids.has(p))) selected.add(d)
|
|
258
|
+
}
|
|
259
|
+
const byUnit = new Map<string, FoldCandidate>()
|
|
260
|
+
for (const d of selected) if (!byUnit.has(d.unit)) byUnit.set(d.unit, d)
|
|
261
|
+
return [...byUnit.values()]
|
|
195
262
|
}
|
|
196
263
|
|
|
197
264
|
/** Exact-name compare, or the shared `*`-glob when the name is a pattern. */
|
|
@@ -250,7 +317,13 @@ export function foldedDeps(node: TaskNode, nodeOf: (id: string) => TaskNode): Fo
|
|
|
250
317
|
candidates.push({ node: dep, unit: foldUnit(dep) })
|
|
251
318
|
}
|
|
252
319
|
if (isGroupTask(node) && node.config.inputs === undefined) return candidates
|
|
253
|
-
return selectFoldedDeps(
|
|
320
|
+
return selectFoldedDeps(
|
|
321
|
+
candidates,
|
|
322
|
+
node.config.inputs?.tasks,
|
|
323
|
+
node.projectName,
|
|
324
|
+
node.id,
|
|
325
|
+
standInsOf(node),
|
|
326
|
+
)
|
|
254
327
|
}
|
|
255
328
|
|
|
256
329
|
/**
|
package/src/graph/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export {
|
|
|
18
18
|
} from './folds.js'
|
|
19
19
|
export { DEFAULT_BUILD, isDefaultBuild } from './default-build.js'
|
|
20
20
|
export { computeReverseDepCount } from './priorities.js'
|
|
21
|
+
export { setStandIns, type StandIns, standInsOf } from './stand-ins.js'
|
|
21
22
|
export { criticalPath } from './critical-path.js'
|
|
22
23
|
export { taskDefinition, taskDefinitions } from './definition.js'
|
|
23
24
|
export {
|
|
@@ -26,6 +27,13 @@ export {
|
|
|
26
27
|
idleServers,
|
|
27
28
|
type InputChanges,
|
|
28
29
|
RestoreDemoted,
|
|
30
|
+
isKeyFailed,
|
|
31
|
+
KeyFailed,
|
|
32
|
+
markKeyFailed,
|
|
33
|
+
markOutputsFailed,
|
|
34
|
+
OutputsFailed,
|
|
35
|
+
outputsFailedOf,
|
|
36
|
+
RestoreFailed,
|
|
29
37
|
runGraph,
|
|
30
38
|
type TaskOutcome,
|
|
31
39
|
} from './scheduler.js'
|
package/src/graph/priorities.ts
CHANGED
|
@@ -347,10 +347,14 @@ export function strategyPriorities(
|
|
|
347
347
|
return out
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
/**
|
|
351
|
+
* A task's merged weight, read as asked: a merged copy held an entry per
|
|
352
|
+
* task of the run beside the two maps it was made of.
|
|
353
|
+
*/
|
|
350
354
|
export function mergePriorities(
|
|
351
355
|
baseline: ReadonlyMap<string, number>,
|
|
352
356
|
overrides: ReadonlyMap<string, number>,
|
|
353
|
-
):
|
|
357
|
+
): (id: string) => number {
|
|
354
358
|
// The override caller scored "expected critical-path duration" in ms.
|
|
355
359
|
// Baseline reverse-deps counts are bounded by N; scale the override so
|
|
356
360
|
// it sorts above the baseline for any node it covers, and add the
|
|
@@ -361,10 +365,9 @@ export function mergePriorities(
|
|
|
361
365
|
const SCALE = 1 << 20
|
|
362
366
|
let lo = 0
|
|
363
367
|
for (const w of overrides.values()) if (w < lo) lo = w
|
|
364
|
-
|
|
365
|
-
for (const [id, w] of overrides) {
|
|
368
|
+
return (id) => {
|
|
366
369
|
const b = baseline.get(id) ?? 0
|
|
367
|
-
|
|
370
|
+
const w = overrides.get(id)
|
|
371
|
+
return w === undefined ? b : (w - lo + 1) * SCALE + b
|
|
368
372
|
}
|
|
369
|
-
return out
|
|
370
373
|
}
|