@vzn/vx 0.0.650 → 0.0.652
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 +7 -23
- package/package.json +5 -5
- package/schemas/last.json +15 -0
- package/schemas/show.json +14 -20
- package/src/bin.ts +21 -31
- package/src/cache/archive.ts +1 -1
- package/src/cache/cache.ts +221 -30
- package/src/cache/file-hashes.ts +3 -0
- package/src/cache/git-inputs.ts +188 -39
- package/src/cache/history-stream.ts +134 -0
- package/src/cache/history-writer.ts +76 -0
- package/src/cache/index.ts +3 -0
- package/src/cache/inputs.ts +77 -14
- package/src/cache/layer.ts +17 -6
- package/src/cache/layered-cache.ts +4 -0
- package/src/cache/linked-inputs.ts +3 -0
- package/src/cache/lockfile-memo.ts +5 -3
- package/src/cache/output-index.ts +5 -2
- package/src/cache/remote-lane.ts +2 -1
- package/src/cache/run-history.ts +56 -17
- package/src/cache/schema.ts +15 -2
- package/src/cli/cache.ts +28 -18
- package/src/cli/completions.ts +4 -2
- package/src/cli/docs.ts +10 -11
- package/src/cli/help.ts +14 -25
- package/src/cli/index.ts +28 -9
- package/src/cli/info.ts +16 -8
- package/src/cli/init.ts +23 -15
- package/src/cli/last.ts +246 -204
- package/src/cli/lock.ts +55 -5
- package/src/cli/plan-format.ts +100 -118
- package/src/cli/plugin-commands.ts +8 -2
- package/src/cli/plugin-templates.ts +36 -45
- package/src/cli/run.ts +46 -27
- package/src/cli/select.ts +43 -5
- package/src/cli/show.ts +45 -52
- package/src/cli/task-verb.ts +22 -0
- package/src/cli/upgrade.ts +3 -1
- package/src/cli/watch-fs.ts +6 -1
- package/src/cli/watch-judge.ts +3 -2
- package/src/cli/watch-set.ts +4 -4
- package/src/cli/watch.ts +94 -52
- package/src/cli/why.ts +123 -89
- package/src/cli/workspace-config.ts +2 -1
- package/src/config.ts +47 -30
- package/src/exec/executor.ts +42 -2
- package/src/exec/index.ts +1 -0
- package/src/exec/kill-tree.ts +58 -5
- package/src/exec/local-executor.ts +1 -1
- package/src/exec/pipes.ts +152 -0
- package/src/exec/runner.ts +199 -182
- package/src/exec/sandbox-binds.ts +1 -1
- package/src/exec/sandbox-paths.ts +3 -1
- package/src/exec/sandbox-runtime.ts +317 -23
- package/src/exec/spawn-off.ts +154 -0
- package/src/exec/task-proxy.ts +93 -0
- package/src/exec/warm-shell.ts +146 -0
- package/src/graph/critical-path.ts +40 -28
- package/src/graph/definition.ts +16 -3
- package/src/graph/priorities.ts +98 -44
- package/src/graph/scheduler.ts +17 -11
- package/src/graph/task-graph.ts +51 -24
- package/src/handoff.ts +10 -7
- package/src/history/failure-mode.ts +24 -1
- package/src/history/history.ts +5 -0
- package/src/history/index.ts +1 -0
- package/src/history/metrics.ts +14 -6
- package/src/index.ts +7 -3
- package/src/orchestrator/admission.ts +1 -1
- package/src/orchestrator/command-windows.ts +1 -1
- package/src/orchestrator/deferred-outputs.ts +1 -1
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/download-policy.ts +5 -7
- package/src/orchestrator/excluded-keys.ts +1 -1
- package/src/orchestrator/execute-task.ts +67 -20
- package/src/orchestrator/graph-memo.ts +50 -1
- package/src/orchestrator/graph-stage.ts +6 -0
- package/src/orchestrator/hit-restore.ts +5 -6
- package/src/orchestrator/index.ts +3 -0
- package/src/orchestrator/keyed-projects.ts +28 -0
- package/src/orchestrator/live-usage.ts +16 -11
- package/src/orchestrator/local-shortcircuit.ts +2 -2
- package/src/orchestrator/locked-digests.ts +63 -0
- package/src/orchestrator/lockfile-claim.ts +108 -29
- package/src/orchestrator/miss-save.ts +58 -11
- package/src/orchestrator/persistent-task.ts +5 -1
- package/src/orchestrator/placement.ts +1 -1
- package/src/orchestrator/plan.ts +7 -3
- package/src/orchestrator/plugin-host.ts +67 -2
- package/src/orchestrator/plugin.ts +53 -2
- package/src/orchestrator/prepare.ts +16 -1
- package/src/orchestrator/projects.ts +4 -1
- package/src/orchestrator/remote-prefetch.ts +1 -1
- package/src/orchestrator/run-context.ts +29 -10
- package/src/orchestrator/run-failures.ts +62 -13
- package/src/orchestrator/run-keys.ts +1 -1
- package/src/orchestrator/run-records.ts +88 -59
- package/src/orchestrator/run-report.ts +1 -1
- package/src/orchestrator/run.ts +145 -41
- package/src/orchestrator/sandbox-request.ts +52 -9
- package/src/orchestrator/shell-verdict.ts +9 -0
- package/src/orchestrator/signals.ts +8 -5
- package/src/orchestrator/stable-keys.ts +17 -18
- package/src/orchestrator/task-artifacts.ts +1 -1
- package/src/orchestrator/task-hash.ts +21 -7
- package/src/orchestrator/telemetry.ts +5 -1
- package/src/report/events.ts +6 -14
- package/src/report/forecast.ts +10 -32
- package/src/report/framed-output.ts +283 -175
- package/src/report/index.ts +8 -3
- package/src/report/logger.ts +74 -39
- package/src/report/plain-output.ts +3 -1
- package/src/report/status-line.ts +49 -357
- package/src/report/summary.ts +105 -136
- package/src/settings.ts +8 -8
- package/src/ui/ansi.ts +48 -0
- package/src/ui/components.ts +506 -0
- package/src/ui/index.ts +79 -0
- package/src/ui/kit.ts +37 -0
- package/src/ui/live-region.ts +363 -0
- package/src/{util/refusal-notice.ts → ui/refusal.ts} +7 -5
- package/src/ui/terminal.ts +51 -0
- package/src/ui/theme.ts +224 -0
- package/src/ui/version-line.ts +28 -0
- package/src/util/config-frame.ts +191 -14
- package/src/util/edit-distance.ts +6 -1
- package/src/util/index.ts +2 -15
- package/src/util/paths.ts +21 -3
- package/src/util/secret-mask.ts +39 -3
- package/src/util/settle.ts +33 -1
- package/src/util/tail.ts +4 -1
- package/src/util/timing.ts +4 -16
- package/src/workspace/affected.ts +89 -30
- package/src/workspace/bracket-literal.ts +2 -2
- package/src/workspace/config-schema.ts +70 -63
- package/src/workspace/dependencies-lock.ts +191 -0
- package/src/workspace/filter.ts +36 -1
- package/src/workspace/index.ts +9 -0
- package/src/workspace/lockfile.ts +159 -6
- package/src/workspace/migration.ts +16 -9
- package/src/workspace/workspace.ts +3 -0
- package/src/util/colors.ts +0 -133
- package/src/util/report.ts +0 -44
- package/src/util/ui.ts +0 -258
- /package/src/{report/path-links.ts → ui/links.ts} +0 -0
- /package/src/{report → ui}/terminal-signals.ts +0 -0
|
@@ -89,14 +89,14 @@ export async function deriveStableKeys(args: DeriveStableKeysArgs): Promise<Stab
|
|
|
89
89
|
// reached through a no-output intermediate still poisons the key, so each
|
|
90
90
|
// dep's accumulated producers fold forward.
|
|
91
91
|
// - outputProjects: the project names of every upstream task declaring
|
|
92
|
-
//
|
|
93
|
-
// own dir), and of every upstream with no cache
|
|
92
|
+
// outputs.files (project-relative outputs land in the producer's
|
|
93
|
+
// own dir), and of every upstream with no `cache: true` that may write
|
|
94
94
|
// in its own project (`undeclaredWriteReach`, item 743). A bitset over
|
|
95
95
|
// project indexes: a union per dep was a string Set copied tasks ×
|
|
96
96
|
// deps × projects times, 101 ms of a 513 ms warm run at 476 packages
|
|
97
97
|
// (item 744).
|
|
98
|
-
// - wsOutputUpstream: any upstream declares
|
|
99
|
-
// (root-anchored, boundary-ignoring outputs), or has no cache
|
|
98
|
+
// - wsOutputUpstream: any upstream declares outputs.workspaceFiles
|
|
99
|
+
// (root-anchored, boundary-ignoring outputs), or has no `cache: true`
|
|
100
100
|
// and a sandbox write grant elsewhere in the workspace, or may rewrite
|
|
101
101
|
// a root file a plugin claims (a root `pnpm install`): every key folds
|
|
102
102
|
// what the claimant took from it, once per run.
|
|
@@ -125,7 +125,7 @@ export async function deriveStableKeys(args: DeriveStableKeysArgs): Promise<Stab
|
|
|
125
125
|
// of what any one reader's upstream producers declare.
|
|
126
126
|
const outputsOf = new Map<string, string[]>()
|
|
127
127
|
for (const n of args.nodes.values()) {
|
|
128
|
-
const files = n.config.
|
|
128
|
+
const files = n.config.outputs?.files ?? []
|
|
129
129
|
if (files.length === 0) continue
|
|
130
130
|
const list = outputsOf.get(n.projectName)
|
|
131
131
|
if (list === undefined) outputsOf.set(n.projectName, [...files])
|
|
@@ -185,10 +185,10 @@ export async function deriveStableKeys(args: DeriveStableKeysArgs): Promise<Stab
|
|
|
185
185
|
const wideOfDep = wideById.get(dep)
|
|
186
186
|
if (wideOfDep !== undefined) wideUnion.addAll(wideOfDep)
|
|
187
187
|
if (wsOutputUpstreamById.get(dep) === true) wsOutputUpstream = true
|
|
188
|
-
const depOut = depNode.config.
|
|
188
|
+
const depOut = depNode.config.outputs
|
|
189
189
|
if ((depOut?.files?.length ?? 0) > 0) outputs.add(depNode.projectName)
|
|
190
190
|
if ((depOut?.workspaceFiles?.length ?? 0) > 0) wsOutputUpstream = true
|
|
191
|
-
// An uncached producer
|
|
191
|
+
// An uncached producer is held to no outputs, yet a same-project reader
|
|
192
192
|
// after it (`tasks: []` decouples the key, not the order) read the
|
|
193
193
|
// bytes it wrote: its key taken up front was the pre-producer one, and
|
|
194
194
|
// the fourth run of seeds A,B,B,A replayed B over A (turborepo#13788).
|
|
@@ -198,7 +198,7 @@ export async function deriveStableKeys(args: DeriveStableKeysArgs): Promise<Stab
|
|
|
198
198
|
wideUnion.add(depNode.projectName)
|
|
199
199
|
} else if (reach === 'workspace') wsOutputUpstream = true
|
|
200
200
|
if (
|
|
201
|
-
depNode.config.cache
|
|
201
|
+
depNode.config.cache !== true &&
|
|
202
202
|
mayWriteFingerprint(depNode, args.workspaceRoot, args.claimedFiles)
|
|
203
203
|
)
|
|
204
204
|
wsOutputUpstream = true
|
|
@@ -219,7 +219,7 @@ export async function deriveStableKeys(args: DeriveStableKeysArgs): Promise<Stab
|
|
|
219
219
|
for (const dep of anyUnfolded ? node.deps : []) {
|
|
220
220
|
const depNode = args.nodes.get(dep)
|
|
221
221
|
if (!depNode) continue
|
|
222
|
-
const cached = depNode.config.cache
|
|
222
|
+
const cached = depNode.config.cache === true
|
|
223
223
|
// The cached twin: a formatter rewriting its own input ahead of a
|
|
224
224
|
// reader that folds no key of it replayed seed B's build under seed A
|
|
225
225
|
// on the fourth run, the same way (item 750).
|
|
@@ -323,7 +323,7 @@ export async function deriveStableKeys(args: DeriveStableKeysArgs): Promise<Stab
|
|
|
323
323
|
)
|
|
324
324
|
if (unstable) unstableById.add(id)
|
|
325
325
|
|
|
326
|
-
const cacheEnabled = node.config.cache
|
|
326
|
+
const cacheEnabled = node.config.cache === true
|
|
327
327
|
if (cacheEnabled && !unstable) stableKeys.push({ hash, node })
|
|
328
328
|
// An uncached key stays preliminary behind any upstream that may write
|
|
329
329
|
// into this project, or anywhere: none of its writes is declared.
|
|
@@ -438,7 +438,7 @@ export function gatedOutputs(
|
|
|
438
438
|
// 1,000 projects' tasks spent 5-8 ms here re-splitting the same globs.
|
|
439
439
|
const outputsOf = new Map<string, ProjectOutputs>()
|
|
440
440
|
for (const n of nodes.values()) {
|
|
441
|
-
const files = n.config.
|
|
441
|
+
const files = n.config.outputs?.files
|
|
442
442
|
if (files === undefined || files.length === 0) continue
|
|
443
443
|
let po = outputsOf.get(n.projectName)
|
|
444
444
|
if (po === undefined) outputsOf.set(n.projectName, (po = new ProjectOutputs()))
|
|
@@ -554,8 +554,8 @@ export function probesAfterWrites(
|
|
|
554
554
|
for (const n of nodes.values()) {
|
|
555
555
|
if (
|
|
556
556
|
commandWriteReach(n, workspaceRoot) !== 'none' ||
|
|
557
|
-
(n.config.
|
|
558
|
-
(n.config.
|
|
557
|
+
(n.config.outputs?.files.length ?? 0) > 0 ||
|
|
558
|
+
(n.config.outputs?.workspaceFiles?.length ?? 0) > 0
|
|
559
559
|
)
|
|
560
560
|
writers.add(n.id)
|
|
561
561
|
}
|
|
@@ -581,7 +581,7 @@ export function probesAfterWrites(
|
|
|
581
581
|
}
|
|
582
582
|
if (writer) writerBelow.add(id)
|
|
583
583
|
if (unfolded) unfoldedBelow.add(id)
|
|
584
|
-
if (unfolded && node.config.cache
|
|
584
|
+
if (unfolded && node.config.cache === true && probes(node)) late.add(id)
|
|
585
585
|
}
|
|
586
586
|
return late
|
|
587
587
|
}
|
|
@@ -628,8 +628,8 @@ function synthUpstream(
|
|
|
628
628
|
* Conservative stability check: does any TRANSITIVE-upstream task whose
|
|
629
629
|
* outputs could land where this task reads its inputs make this task's
|
|
630
630
|
* key preliminary? The producers are pre-folded by `deriveStableKeys`
|
|
631
|
-
* (`upstreamOutputProjects` = projects declaring `
|
|
632
|
-
* upstream; `hasWsOutputUpstream` = any `
|
|
631
|
+
* (`upstreamOutputProjects` = projects declaring `outputs.files`
|
|
632
|
+
* upstream; `hasWsOutputUpstream` = any `outputs.workspaceFiles`
|
|
633
633
|
* upstream), so a producer reached through a no-output intermediate is
|
|
634
634
|
* caught too.
|
|
635
635
|
*
|
|
@@ -663,7 +663,6 @@ export function dependsOnSiblingOutputs(
|
|
|
663
663
|
readableOf?: ReadonlyMap<string, readonly string[]>
|
|
664
664
|
},
|
|
665
665
|
): boolean {
|
|
666
|
-
const cache = node.config.cache
|
|
667
666
|
const declared = node.config.inputs
|
|
668
667
|
// A task with no inputs has no key to take up front.
|
|
669
668
|
if (declared === undefined) return false
|
|
@@ -697,7 +696,7 @@ export function dependsOnSiblingOutputs(
|
|
|
697
696
|
// that producer, its restore raced the producer's clean and extract
|
|
698
697
|
// (M-5; invariant 2 of docs/design/overlapping-outputs-2026-09.md).
|
|
699
698
|
// `outputsOf` holds this task's own globs too; set them aside once.
|
|
700
|
-
const own =
|
|
699
|
+
const own = node.config.outputs?.files ?? []
|
|
701
700
|
if (own.length > 0) {
|
|
702
701
|
const others = [...outputs]
|
|
703
702
|
for (const g of own) {
|
|
@@ -22,7 +22,7 @@ export interface ArtifactTask {
|
|
|
22
22
|
/** Absolute. */
|
|
23
23
|
readonly projectDir: string
|
|
24
24
|
readonly command: string
|
|
25
|
-
/** Its `
|
|
25
|
+
/** Its `outputs`: project-relative `files`, root-relative `workspaceFiles`. */
|
|
26
26
|
readonly outputs: {
|
|
27
27
|
readonly files: readonly string[]
|
|
28
28
|
readonly workspaceFiles: readonly string[]
|
|
@@ -292,7 +292,7 @@ export async function describeTaskInputs(args: ComputeHashArgs): Promise<{
|
|
|
292
292
|
* input changed and changed BACK while the command ran matched its digest
|
|
293
293
|
* again, and the entry saved the other content's output under the key (item
|
|
294
294
|
* 1015). A write before it — an upstream's — is judged by content, as before.
|
|
295
|
-
* `byBytes` (`
|
|
295
|
+
* `byBytes` (`inputs.stability.strategy: 'mutable'`) judges a write during
|
|
296
296
|
* the command by content too, when it answers true for that file and its
|
|
297
297
|
* ctime: the task says the same bytes are its own identical rewrite
|
|
298
298
|
* (tsdown's package.json), not an edit undone, unless another command that
|
|
@@ -353,14 +353,14 @@ export function taskFilesToken(
|
|
|
353
353
|
/** What `resolveInputs` is asked for a task's key. */
|
|
354
354
|
function inputsArgs(args: ComputeHashArgs): ResolveInputsArgs {
|
|
355
355
|
const cfg = args.node.config
|
|
356
|
-
const
|
|
356
|
+
const cfgOut = cfg.outputs
|
|
357
357
|
return {
|
|
358
358
|
projectDir: args.node.projectDir,
|
|
359
359
|
workspaceRoot: args.workspaceRoot,
|
|
360
360
|
envSource: process.env,
|
|
361
361
|
inputs: cfg.inputs,
|
|
362
|
-
ownOutputs:
|
|
363
|
-
ownWorkspaceOutputs:
|
|
362
|
+
ownOutputs: cfgOut?.files ?? [],
|
|
363
|
+
ownWorkspaceOutputs: cfgOut?.workspaceFiles ?? [],
|
|
364
364
|
nestedProjectDirs: args.nestedProjectDirs,
|
|
365
365
|
...(args.gitFilesCache !== undefined ? { gitFilesCache: args.gitFilesCache } : {}),
|
|
366
366
|
...(args.probesAfterWrites?.has(args.node.id) === true ? { runtimeScope: args.node.id } : {}),
|
|
@@ -519,9 +519,23 @@ function hashableConfig(cfg: TaskConfig): unknown {
|
|
|
519
519
|
// Keys sorted: moving `cache` above `exec`, or a `define` in another
|
|
520
520
|
// order, missed every key of the task, and a `project` or `graph` plugin
|
|
521
521
|
// writes a config the lock never sorted (2026-10-25).
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
|
|
522
|
+
const env = cfg.inputs?.env
|
|
523
|
+
const pass = cfg.exec?.env?.passThrough
|
|
524
|
+
if (cfg.exec?.remote === undefined && sortedNames(env) && sortedNames(pass))
|
|
525
|
+
return keysSorted(cfg)
|
|
526
|
+
const { remote: _remote, ...execRest } = cfg.exec ?? ({} as NonNullable<TaskConfig['exec']>)
|
|
527
|
+
// Env names are sets: each value is read by its name, so their order
|
|
528
|
+
// keys nothing (conformance env/declared-order-irrelevant).
|
|
529
|
+
if (!sortedNames(pass)) execRest.env = { ...execRest.env, passThrough: [...pass!].sort() }
|
|
530
|
+
const out: TaskConfig = cfg.exec === undefined ? { ...cfg } : { ...cfg, exec: execRest }
|
|
531
|
+
if (!sortedNames(env)) out.inputs = { ...cfg.inputs!, env: [...env!].sort() }
|
|
532
|
+
return keysSorted(out)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function sortedNames(names: readonly string[] | undefined): boolean {
|
|
536
|
+
if (names === undefined) return true
|
|
537
|
+
for (let i = 1; i < names.length; i++) if (names[i - 1]! > names[i]!) return false
|
|
538
|
+
return true
|
|
525
539
|
}
|
|
526
540
|
|
|
527
541
|
/**
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// what or how tasks run. Contrast `cache`/`executor`, which return objects
|
|
17
17
|
// core calls INTO; those are the behavior capabilities, kept separate.
|
|
18
18
|
|
|
19
|
-
import { sampleTrees, type MachineReport } from '../exec/index.js'
|
|
19
|
+
import { sampleTrees, type MachineReport, type WorkerAdvice } from '../exec/index.js'
|
|
20
20
|
import {
|
|
21
21
|
type CacheSource,
|
|
22
22
|
deriveCacheSource,
|
|
@@ -301,6 +301,8 @@ export interface RunSummaryRecord {
|
|
|
301
301
|
* (`TaskExecutor.idealMs`), beside `totalDurationMs`. Additive.
|
|
302
302
|
*/
|
|
303
303
|
idealMs?: number
|
|
304
|
+
/** The workers that would have run it best (`TaskExecutor.advice`). Additive. */
|
|
305
|
+
advice?: readonly WorkerAdvice[]
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
/** One stage of a run, a wall window in epoch ms. */
|
|
@@ -334,6 +336,7 @@ export function assembleRunSummary(
|
|
|
334
336
|
uploads?: { count: number; bytes: number; ms: number; failed: number }
|
|
335
337
|
machines?: readonly MachineReport[]
|
|
336
338
|
idealMs?: number | undefined
|
|
339
|
+
advice?: readonly WorkerAdvice[]
|
|
337
340
|
},
|
|
338
341
|
): RunSummaryRecord {
|
|
339
342
|
let failedCount = 0
|
|
@@ -376,6 +379,7 @@ export function assembleRunSummary(
|
|
|
376
379
|
? { machines: timing.machines }
|
|
377
380
|
: {}),
|
|
378
381
|
...(timing.idealMs !== undefined ? { idealMs: timing.idealMs } : {}),
|
|
382
|
+
...(timing.advice !== undefined && timing.advice.length > 0 ? { advice: timing.advice } : {}),
|
|
379
383
|
}
|
|
380
384
|
}
|
|
381
385
|
|
package/src/report/events.ts
CHANGED
|
@@ -14,12 +14,12 @@ import type { Logger } from './logger.js'
|
|
|
14
14
|
import type { RunContext } from './summary.js'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* A task no cache answered for: it
|
|
17
|
+
* A task no cache answered for: it has no `cache: true`, or the run's policy
|
|
18
18
|
* read and wrote nothing. Its run is `no-cache`, never a miss, on every
|
|
19
19
|
* surface — `--dry`'s plan, the frame, the footer, the run history.
|
|
20
20
|
*/
|
|
21
21
|
export function ranNoCache(o: { node: TaskNode; cacheOff?: boolean }): boolean {
|
|
22
|
-
return o.node.config.cache
|
|
22
|
+
return o.node.config.cache !== true || o.cacheOff === true
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Payload of the `run:start` event — mirrors the Logger.runStart hook. */
|
|
@@ -45,7 +45,7 @@ export interface RunStartInfo {
|
|
|
45
45
|
*/
|
|
46
46
|
export type RunEvent =
|
|
47
47
|
| { kind: 'run:start'; info: RunStartInfo }
|
|
48
|
-
| { kind: 'task:start'; node: TaskNode
|
|
48
|
+
| { kind: 'task:start'; node: TaskNode }
|
|
49
49
|
| { kind: 'task:stdout'; node: TaskNode; chunk: string }
|
|
50
50
|
| { kind: 'task:stderr'; node: TaskNode; chunk: string }
|
|
51
51
|
| { kind: 'task:complete'; node: TaskNode; outcome: TaskOutcome }
|
|
@@ -139,12 +139,7 @@ export function busLogger(bus: EventBus): Logger {
|
|
|
139
139
|
taskStderr: (node, chunk) => bus.emit({ kind: 'task:stderr', node, chunk }),
|
|
140
140
|
taskComplete: (node, outcome) => bus.emit({ kind: 'task:complete', node, outcome }),
|
|
141
141
|
runStart: (info) => bus.emit({ kind: 'run:start', info }),
|
|
142
|
-
taskStart: (node,
|
|
143
|
-
bus.emit(
|
|
144
|
-
info?.waitedMs === undefined
|
|
145
|
-
? { kind: 'task:start', node }
|
|
146
|
-
: { kind: 'task:start', node, waitedMs: info.waitedMs },
|
|
147
|
-
),
|
|
142
|
+
taskStart: (node) => bus.emit({ kind: 'task:start', node }),
|
|
148
143
|
runEnd: () => {
|
|
149
144
|
if (ended) return
|
|
150
145
|
ended = true
|
|
@@ -179,10 +174,7 @@ export function terminalSubscriber(sink: Logger): RunEventSubscriber {
|
|
|
179
174
|
sink.runStart?.(event.info)
|
|
180
175
|
return
|
|
181
176
|
case 'task:start':
|
|
182
|
-
sink.taskStart?.(
|
|
183
|
-
event.node,
|
|
184
|
-
event.waitedMs === undefined ? undefined : { waitedMs: event.waitedMs },
|
|
185
|
-
)
|
|
177
|
+
sink.taskStart?.(event.node)
|
|
186
178
|
return
|
|
187
179
|
case 'task:stdout':
|
|
188
180
|
sink.taskStdout(event.node, event.chunk)
|
|
@@ -289,7 +281,7 @@ export interface OutcomeView {
|
|
|
289
281
|
* and counting groups as executed tasks drifts from the terminal.
|
|
290
282
|
*/
|
|
291
283
|
isGroup?: boolean
|
|
292
|
-
/** Executed
|
|
284
|
+
/** Executed without `cache: true`: never consulted the cache, not a miss. */
|
|
293
285
|
noCache?: boolean
|
|
294
286
|
/** Cache hits only: the exec time the entry was stored with — the work
|
|
295
287
|
* this hit skipped, as opposed to `durationMs`, the restore it cost. */
|
package/src/report/forecast.ts
CHANGED
|
@@ -12,8 +12,6 @@ export interface LiveForecast {
|
|
|
12
12
|
critical?: string
|
|
13
13
|
/** Tasks not started because a dependency has not finished: the tasks line's `blocked`. */
|
|
14
14
|
waiting: number
|
|
15
|
-
/** Each running task's p50, where history knows it: the row's expected duration. */
|
|
16
|
-
expected?: ReadonlyMap<string, number>
|
|
17
15
|
/**
|
|
18
16
|
* Tasks not started because a dependency has not finished, in the order
|
|
19
17
|
* the scheduler would start them, at most one per worker: what an idle
|
|
@@ -21,13 +19,12 @@ export interface LiveForecast {
|
|
|
21
19
|
*/
|
|
22
20
|
blocked?: readonly BlockedTask[]
|
|
23
21
|
/**
|
|
24
|
-
* Each running task's count of
|
|
25
|
-
*
|
|
26
|
-
* are not running. A
|
|
22
|
+
* Each running task's count of what is downstream of it, the
|
|
23
|
+
* scheduler's own count (`computeReverseDepCount`): its row's `blocks`
|
|
24
|
+
* cell, why they are not running. A node downstream of two running tasks
|
|
25
|
+
* counts under both.
|
|
27
26
|
*/
|
|
28
27
|
blockedBy: ReadonlyMap<string, number>
|
|
29
|
-
/** Each task's usual time (its executed p50), for the results' `±usual`. */
|
|
30
|
-
usual?: ReadonlyMap<string, number>
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
/** A task an unfinished dependency holds back, and what holds it: the running task its longest chain waits on, else that dependency. */
|
|
@@ -54,8 +51,8 @@ export function createForecast(
|
|
|
54
51
|
nodes: ReadonlyMap<string, TaskNode>,
|
|
55
52
|
p50s: ReadonlyMap<string, number>,
|
|
56
53
|
concurrency: number,
|
|
57
|
-
/**
|
|
58
|
-
|
|
54
|
+
/** Every node's dependents count, the scheduler's baseline and the rows' `blocks`; computed here when absent. */
|
|
55
|
+
rank: ReadonlyMap<string, number> = computeReverseDepCount(nodes),
|
|
59
56
|
): Forecast {
|
|
60
57
|
// Dependencies before dependents, once: each call is one pass over it.
|
|
61
58
|
const order: TaskNode[] = []
|
|
@@ -87,9 +84,8 @@ export function createForecast(
|
|
|
87
84
|
if (p !== undefined) cost.set(n.id, p)
|
|
88
85
|
}
|
|
89
86
|
const workers = Math.max(1, concurrency)
|
|
90
|
-
// The scheduler's own baseline ranking, most dependents first,
|
|
91
|
-
// graph order
|
|
92
|
-
const rank = computeReverseDepCount(nodes)
|
|
87
|
+
// The scheduler's own baseline ranking (`rank`), most dependents first,
|
|
88
|
+
// ties in graph order.
|
|
93
89
|
const position = new Map(order.map((n, i) => [n.id, i]))
|
|
94
90
|
const before = (a: string, b: string): boolean => {
|
|
95
91
|
const d = (rank.get(a) ?? 0) - (rank.get(b) ?? 0)
|
|
@@ -97,12 +93,7 @@ export function createForecast(
|
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
return (finished, running, nowMs) => {
|
|
100
|
-
const expected = new Map<string, number>()
|
|
101
96
|
let waiting = 0
|
|
102
|
-
for (const id of running.keys()) {
|
|
103
|
-
const p = cost.get(id)
|
|
104
|
-
if (p !== undefined) expected.set(id, p)
|
|
105
|
-
}
|
|
106
97
|
const end = new Map<string, number>()
|
|
107
98
|
// The running task each node's longest chain waits on.
|
|
108
99
|
const head = new Map<string, string>()
|
|
@@ -153,30 +144,17 @@ export function createForecast(
|
|
|
153
144
|
work += own
|
|
154
145
|
}
|
|
155
146
|
for (const id of running.keys()) {
|
|
156
|
-
const d =
|
|
147
|
+
const d = rank.get(id)
|
|
157
148
|
if (d !== undefined && d > 0) blockedBy.set(id, d)
|
|
158
149
|
}
|
|
159
|
-
if (!known) return { waiting,
|
|
150
|
+
if (!known) return { waiting, blocked: held, blockedBy }
|
|
160
151
|
const critical = chainEnd === undefined ? undefined : head.get(chainEnd)
|
|
161
152
|
return {
|
|
162
153
|
etaMs: Math.max(chain, work / workers),
|
|
163
154
|
...(critical !== undefined ? { critical } : {}),
|
|
164
155
|
waiting,
|
|
165
|
-
expected,
|
|
166
156
|
blocked: held,
|
|
167
157
|
blockedBy,
|
|
168
|
-
usual: cost,
|
|
169
158
|
}
|
|
170
159
|
}
|
|
171
160
|
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Each task's `blocks`: how many tasks, a group's own looked through and
|
|
175
|
-
* none counted, sit downstream of it in the run, each once (2026-10-29:
|
|
176
|
-
* every task it holds back, not only the next ones).
|
|
177
|
-
*/
|
|
178
|
-
export function blocksCounts(nodes: ReadonlyMap<string, TaskNode>): Map<string, number> {
|
|
179
|
-
const tasks = new Map<string, true>()
|
|
180
|
-
for (const n of nodes.values()) if (!isGroupTask(n)) tasks.set(n.id, true)
|
|
181
|
-
return computeReverseDepCount(nodes, tasks)
|
|
182
|
-
}
|