@vzn/vx 0.0.637 → 0.0.639

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.
Files changed (126) hide show
  1. package/docs/caching.md +84 -28
  2. package/docs/cli.md +273 -408
  3. package/docs/execution.md +19 -44
  4. package/docs/features.md +13 -15
  5. package/docs/patterns.md +5 -7
  6. package/docs/schema.md +68 -61
  7. package/package.json +5 -5
  8. package/schemas/last.json +7 -4
  9. package/schemas/plan.json +2 -2
  10. package/skills/vx/SKILL.md +19 -6
  11. package/src/bin.ts +13 -7
  12. package/src/cache/cache.ts +60 -11
  13. package/src/cache/git-inputs.ts +6 -0
  14. package/src/cache/index.ts +10 -2
  15. package/src/cache/inputs.ts +0 -24
  16. package/src/cache/layer.ts +32 -12
  17. package/src/cache/layered-cache.ts +112 -315
  18. package/src/cache/plugin-cache.ts +240 -0
  19. package/src/cache/policy.ts +9 -0
  20. package/src/cache/remote-lane.ts +383 -0
  21. package/src/cache/run-history.ts +92 -8
  22. package/src/cache/run-store.ts +77 -0
  23. package/src/cache/schema.ts +44 -13
  24. package/src/cache/scoped-cache.ts +22 -2
  25. package/src/cli/cache.ts +33 -16
  26. package/src/cli/completions.ts +11 -7
  27. package/src/cli/foreign-flags.ts +10 -22
  28. package/src/cli/help.ts +22 -38
  29. package/src/cli/index.ts +25 -11
  30. package/src/cli/info.ts +8 -6
  31. package/src/cli/init.ts +14 -15
  32. package/src/cli/last.ts +20 -10
  33. package/src/cli/lock.ts +39 -27
  34. package/src/cli/plan-format.ts +10 -61
  35. package/src/cli/plugin-commands.ts +77 -6
  36. package/src/cli/plugin-templates.ts +1 -1
  37. package/src/cli/run.ts +31 -191
  38. package/src/cli/select.ts +18 -9
  39. package/src/cli/show.ts +72 -40
  40. package/src/cli/task-verb.ts +4 -4
  41. package/src/cli/upgrade.ts +132 -39
  42. package/src/cli/watch-cycle.ts +2 -1
  43. package/src/cli/watch-filter.ts +7 -2
  44. package/src/cli/watch-judge.ts +6 -1
  45. package/src/cli/watch.ts +73 -34
  46. package/src/cli/why.ts +14 -5
  47. package/src/cli/workspace-config.ts +2 -2
  48. package/src/config.ts +0 -6
  49. package/src/exec/executor.ts +1 -1
  50. package/src/exec/sandbox-runtime.ts +1 -1
  51. package/src/exec/sandbox-violations.ts +3 -1
  52. package/src/graph/index.ts +9 -1
  53. package/src/graph/priorities.ts +1 -1
  54. package/src/graph/scheduler.ts +12 -16
  55. package/src/graph/task-graph.ts +119 -21
  56. package/src/graph/task-status.ts +95 -0
  57. package/src/{orchestrator → history}/failure-mode.ts +20 -11
  58. package/src/{orchestrator → history}/history.ts +61 -5
  59. package/src/history/index.ts +48 -0
  60. package/src/{orchestrator → history}/metrics.ts +14 -23
  61. package/src/{orchestrator → history}/miss-reason.ts +1 -1
  62. package/src/index.ts +21 -29
  63. package/src/orchestrator/affected-tasks.ts +8 -3
  64. package/src/orchestrator/doctor.ts +2 -2
  65. package/src/orchestrator/execute-args.ts +80 -0
  66. package/src/orchestrator/execute-task.ts +47 -429
  67. package/src/orchestrator/fingerprint-watch.ts +2 -2
  68. package/src/orchestrator/index.ts +3 -74
  69. package/src/orchestrator/miss-save.ts +33 -3
  70. package/src/orchestrator/options.ts +1 -36
  71. package/src/orchestrator/output-host.ts +4 -3
  72. package/src/orchestrator/persistent-task.ts +321 -0
  73. package/src/orchestrator/persistent.ts +130 -3
  74. package/src/orchestrator/placement.ts +3 -3
  75. package/src/orchestrator/plan.ts +18 -5
  76. package/src/orchestrator/plugin-host.ts +77 -47
  77. package/src/orchestrator/plugin.ts +86 -14
  78. package/src/orchestrator/prepare.ts +74 -31
  79. package/src/orchestrator/projects.ts +3 -0
  80. package/src/orchestrator/remote-prefetch.ts +23 -6
  81. package/src/orchestrator/run-artifacts.ts +5 -177
  82. package/src/orchestrator/run-context.ts +55 -0
  83. package/src/orchestrator/run-failures.ts +15 -3
  84. package/src/orchestrator/run-records.ts +36 -4
  85. package/src/orchestrator/run-report.ts +81 -179
  86. package/src/orchestrator/run-settings.ts +1 -16
  87. package/src/orchestrator/run-stop.ts +153 -0
  88. package/src/orchestrator/run.ts +313 -555
  89. package/src/orchestrator/sandbox-fix.ts +0 -16
  90. package/src/orchestrator/signals.ts +14 -6
  91. package/src/orchestrator/stable-keys.ts +107 -3
  92. package/src/orchestrator/steps.ts +50 -0
  93. package/src/orchestrator/task-hash.ts +3 -16
  94. package/src/orchestrator/task-log-buffer.ts +1 -1
  95. package/src/orchestrator/task-process.ts +94 -0
  96. package/src/orchestrator/telemetry-host.ts +1 -1
  97. package/src/orchestrator/telemetry.ts +6 -83
  98. package/src/{orchestrator → report}/events.ts +41 -8
  99. package/src/{orchestrator → report}/forecast.ts +35 -2
  100. package/src/{orchestrator → report}/framed-output.ts +39 -34
  101. package/src/report/index.ts +36 -0
  102. package/src/{orchestrator → report}/logger.ts +166 -21
  103. package/src/{orchestrator → report}/status-line.ts +84 -7
  104. package/src/{orchestrator → report}/summary.ts +179 -63
  105. package/src/report/tally.ts +83 -0
  106. package/src/settings.ts +13 -23
  107. package/src/{orchestrator → util}/colors.ts +49 -4
  108. package/src/util/config-frame.ts +31 -2
  109. package/src/util/errors.ts +32 -0
  110. package/src/util/git-ignored.ts +143 -0
  111. package/src/util/index.ts +25 -3
  112. package/src/util/refusal-notice.ts +111 -0
  113. package/src/util/report.ts +39 -0
  114. package/src/util/settle.ts +14 -39
  115. package/src/util/tail.ts +1 -1
  116. package/src/util/ui.ts +197 -0
  117. package/src/workspace/affected.ts +20 -23
  118. package/src/workspace/dependency-rules.ts +12 -4
  119. package/src/workspace/migration.ts +87 -91
  120. package/src/workspace/workspace.ts +5 -4
  121. package/schemas/summary.json +0 -133
  122. package/src/cache/chained-cache.ts +0 -276
  123. package/src/orchestrator/tally.ts +0 -112
  124. /package/src/{orchestrator → report}/path-links.ts +0 -0
  125. /package/src/{orchestrator → report}/plain-output.ts +0 -0
  126. /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
@@ -15,11 +15,13 @@ import { statSync } from 'node:fs'
15
15
  import path from 'node:path'
16
16
  // The keyed-run predicate lives beside the schema in cache/cache.ts, so a
17
17
  // rule written once cannot drift.
18
- import { KEYED_RUNS_SQL } from '../cache/index.js'
19
- import { splitTaskId } from '../graph/index.js'
18
+ import { KEYED_RUNS_SQL, type TaskInputComponent } from '../cache/index.js'
19
+ import { isPassStatus, splitTaskId } from '../graph/index.js'
20
20
  import { clampInt, MASKED, secretNamed } from '../util/index.js'
21
- import type { TaskInputComponent } from './task-hash.js'
22
- import { isPassStatus } from './telemetry.js'
21
+
22
+ // A recorded `cache_policy` is the `--cache-policy` spec (`local:rw,remote:r`).
23
+ const readsCache = (spec: string): boolean => /(?:^|,)(?:local|remote):w?r/.test(spec)
24
+ const writesCache = (spec: string): boolean => /(?:^|,)(?:local|remote):r?w/.test(spec)
23
25
 
24
26
  // ---------------------------------------------------------------------------
25
27
  // Run listing + detail
@@ -53,6 +55,8 @@ export interface RunSummaryRow {
53
55
  notReady: 'timeout' | 'exited' | 'spawn' | null
54
56
  /** The task ran with the run's forwarded args (`-- …`): a requested one under a run that had some. */
55
57
  forwarded: boolean
58
+ /** Each remote read or write of its key that failed, `upload: <message>`, `; `-joined (v40). */
59
+ remoteError: string | null
56
60
  }
57
61
 
58
62
  export interface ListRunsArgs {
@@ -113,7 +117,7 @@ export function listRuns(db: Database, args: ListRunsArgs = {}): RunSummaryRow[]
113
117
  wallclock_start_ns AS wallclockStartNs, wallclock_end_ns AS wallclockEndNs,
114
118
  blocked_by AS blockedBy, timed_out AS timedOut,
115
119
  sandbox_violations AS sandboxViolations, not_ready AS notReady,
116
- forward_args IS NOT NULL AS forwarded
120
+ forward_args IS NOT NULL AS forwarded, remote_error AS remoteError
117
121
  FROM runs ${clause} ORDER BY id DESC LIMIT ?`,
118
122
  )
119
123
  .all(...params, limit) as RawRow[]
@@ -131,8 +135,7 @@ export function listRuns(db: Database, args: ListRunsArgs = {}): RunSummaryRow[]
131
135
 
132
136
  /**
133
137
  * One `vx run` invocation header — every `invocations` column, camelCased,
134
- * `tags` parsed to a `Record<string,string>` and `dirty`/`ci`/`exitOk`
135
- * surfaced as booleans. The SPA's
138
+ * `dirty`/`ci`/`exitOk` surfaced as booleans. The SPA's
136
139
  * existing fields (runId / startedAt / endedAt / taskCount / failedCount /
137
140
  * hitCount / totalDurationMs) are all present, so existing views keep working.
138
141
  */
@@ -166,7 +169,6 @@ export interface InvocationDetail {
166
169
  os: string | null
167
170
  arch: string | null
168
171
  vxVersion: string
169
- tags: Record<string, string>
170
172
  }
171
173
 
172
174
  interface InvocationRawRow {
@@ -197,7 +199,6 @@ interface InvocationRawRow {
197
199
  os: string | null
198
200
  arch: string | null
199
201
  vxVersion: string
200
- tags: string
201
202
  }
202
203
 
203
204
  const INVOCATION_COLUMNS = `
@@ -210,7 +211,7 @@ const INVOCATION_COLUMNS = `
210
211
  restored_remote_count AS restoredRemoteCount,
211
212
  exit_ok AS exitOk,
212
213
  commit_sha AS commitSha, branch, dirty, ci, ci_provider AS ciProvider,
213
- host, os, arch, vx_version AS vxVersion, tags`
214
+ host, os, arch, vx_version AS vxVersion`
214
215
 
215
216
  function mapInvocation(r: InvocationRawRow): InvocationDetail {
216
217
  let requestedTasks: string[] = []
@@ -218,15 +219,6 @@ function mapInvocation(r: InvocationRawRow): InvocationDetail {
218
219
  const parsed = JSON.parse(r.requestedTasks) as unknown
219
220
  if (Array.isArray(parsed)) requestedTasks = parsed.map(String)
220
221
  } catch {}
221
- let tags: Record<string, string> = {}
222
- try {
223
- const parsed = JSON.parse(r.tags) as unknown
224
- if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
225
- tags = Object.fromEntries(
226
- Object.entries(parsed as Record<string, unknown>).map(([k, v]) => [k, String(v)]),
227
- )
228
- }
229
- } catch {}
230
222
  return {
231
223
  runId: r.runId,
232
224
  command: r.command,
@@ -255,7 +247,6 @@ function mapInvocation(r: InvocationRawRow): InvocationDetail {
255
247
  os: r.os,
256
248
  arch: r.arch,
257
249
  vxVersion: r.vxVersion,
258
- tags,
259
250
  }
260
251
  }
261
252
 
@@ -412,7 +403,7 @@ function unchangedKeyNote(
412
403
  p: string
413
404
  } | null
414
405
  )?.p
415
- if (policy !== undefined && !policy.split(',').some((axis) => axis === 'lR' || axis === 'rR')) {
406
+ if (policy !== undefined && !readsCache(policy)) {
416
407
  return 'cache key unchanged — re-executed because this run did not read the cache (--force, or a --cache-policy without read)'
417
408
  }
418
409
  // `get` answers null, not undefined, for no row.
@@ -438,7 +429,7 @@ function unchangedKeyNote(
438
429
  const row = db
439
430
  .query('SELECT cache_policy AS p, failed_count AS f FROM invocations WHERE run_id = ?')
440
431
  .get(id) as { p: string; f: number } | null
441
- const writes = row?.p.split(',').some((axis) => axis === 'lW' || axis === 'rW') === true
432
+ const writes = row !== null && writesCache(row.p)
442
433
  return writes ? (row?.f ?? 0) : 0
443
434
  }
444
435
  const before = failedWithWrites(prev.runId)
@@ -472,7 +463,7 @@ function unchangedKeyNote(
472
463
  p: string
473
464
  } | null
474
465
  )?.p
475
- if (prevPolicy !== undefined && !prevPolicy.split(',').some((a) => a === 'lW' || a === 'rW')) {
466
+ if (prevPolicy !== undefined && !writesCache(prevPolicy)) {
476
467
  return 'cache key unchanged — the previous run on this key did not write the cache (--no-cache, or a --cache-policy without write), so there was nothing to hit'
477
468
  }
478
469
  return 'cache key unchanged — the previous run on this key executed but no entry for it is in the cache (its save failed, or it was pruned since), so there was nothing to hit'
@@ -9,7 +9,7 @@
9
9
  import type { Database } from 'bun:sqlite'
10
10
  import type { InputChanges } from '../graph/index.js'
11
11
  import { diffKeyComponents, loadEntryInputs } from './metrics.js'
12
- import type { TaskInputComponent } from './task-hash.js'
12
+ import type { TaskInputComponent } from '../cache/index.js'
13
13
 
14
14
  /** How many changed components a miss names; `count` says how many there were. */
15
15
  const NAMED = 10
package/src/index.ts CHANGED
@@ -56,13 +56,9 @@ export { run, planRun, prepareRun } from './orchestrator/index.js'
56
56
  // The per-task duration history a `schedule` plugin learns from
57
57
  // (`@vzn/vx-schedule-history` does; a plugin package reaches core only
58
58
  // through this façade, which is what put these here).
59
- export { currentBranch, LocalHistoryProvider } from './orchestrator/index.js'
60
- export type {
61
- HistoryProvider,
62
- HistoryTable,
63
- TaskHistory,
64
- TaskResources,
65
- } from './orchestrator/index.js'
59
+ export { currentBranch } from './orchestrator/index.js'
60
+ export { LocalHistoryProvider } from './history/index.js'
61
+ export type { HistoryProvider, HistoryTable, TaskHistory, TaskResources } from './history/index.js'
66
62
  export type { PreparedRun } from './orchestrator/index.js'
67
63
  // `RunOptions.affected`: what `--affected`'s diff changed, per project.
68
64
  export type { AffectedChanges } from './workspace/index.js'
@@ -72,14 +68,8 @@ export type { AffectedChanges } from './workspace/index.js'
72
68
  // docs/cli.md § Programmatic API listed all three (item 387, 2026-09-19).
73
69
  export type { RunPlan, PlannedTask } from './orchestrator/index.js'
74
70
  export type { CiContext, GitContext, HostContext, WorkspaceIdentity } from './orchestrator/index.js'
75
- export type {
76
- CacheFlags,
77
- CachePolicy,
78
- Logger,
79
- OutputView,
80
- RunOptions,
81
- RunSummary,
82
- } from './orchestrator/index.js'
71
+ export type { CacheFlags, CachePolicy, RunOptions, RunSummary } from './orchestrator/index.js'
72
+ export type { Logger, OutputView } from './report/index.js'
83
73
  // `splitTaskId` is on the façade because the alternative is what happened:
84
74
  // with only `taskId()` to JOIN an id and nothing exported to SPLIT one,
85
75
  // consumers roll their own `split('#', 2)` and drift from the graph, which
@@ -89,7 +79,13 @@ export type { TaskNode, TaskOutcome, TaskStatus } from './graph/index.js'
89
79
 
90
80
  // Cache classes + the layer interface (the `cache` capability's currency).
91
81
  export { Cache } from './cache/index.js'
92
- export type { CacheLayer, RemoteCacheLayer, RunRecord, InvocationRecord } from './cache/index.js'
82
+ export type {
83
+ CacheLayer,
84
+ PluginCache,
85
+ RemoteCacheLayer,
86
+ RunRecord,
87
+ InvocationRecord,
88
+ } from './cache/index.js'
93
89
 
94
90
  // Workspace discovery + the project/config catalog surface — an
95
91
  // out-of-process service/CLI needs these. `loadProjectConfig` is the RAW
@@ -118,6 +114,8 @@ export {
118
114
  quoteTsLiteral,
119
115
  taskNameProblem,
120
116
  } from './workspace/index.js'
117
+ // Output components: a plugin draws its lines with core's, so they look alike.
118
+ export { ui } from './util/index.js'
121
119
  export type {
122
120
  ApplyMigrationArgs,
123
121
  MigrationFormat,
@@ -166,10 +164,12 @@ export type {
166
164
  DiscoverContext,
167
165
  DiscoveredProject,
168
166
  PluginSetupContext,
167
+ RunReport,
168
+ RunReportTask,
169
169
  PluginHookName,
170
170
  PluginHookHandlers,
171
- RunStartInfo,
172
171
  } from './orchestrator/index.js'
172
+ export type { RunStartInfo } from './report/index.js'
173
173
  // The per-task execution contract a plugin's `executor` capability returns.
174
174
  // (`runCommand` / `runSandboxed`, the local executor's own primitives, left
175
175
  // the façade 2026-09-10: no executor plugin built on them — `@vzn/vx-reapi`
@@ -201,15 +201,8 @@ export type {
201
201
  // consumer rolls its own Set of status literals — and a Set has no
202
202
  // compile-time tripwire when the union gains a member, so it silently answers
203
203
  // "no" for the new one.
204
- // `escapeMarkdownCell` is on the façade for the same demonstrated need: a
205
- // plugin that renders a run as a markdown table takes the same unvalidated
206
- // task names core does, and the cloud job summary shipped without the escape.
207
- export {
208
- escapeMarkdownCell,
209
- TELEMETRY_SCHEMA_VERSION,
210
- isCacheHit,
211
- isPassStatus,
212
- } from './orchestrator/index.js'
204
+ export { type CacheSource, isCacheHit, isPassStatus } from './graph/index.js'
205
+ export { TELEMETRY_SCHEMA_VERSION } from './orchestrator/index.js'
213
206
  // `TaskLogBuffer` is on the façade on the same demonstrated need: EVERY
214
207
  // telemetry sink that ships build output has to bound it, and the retention
215
208
  // rules (per-task tail, per-run budget, failures never evicted by successes,
@@ -221,7 +214,6 @@ export { LOG_WIRE_VERSION, TaskLogBuffer } from './orchestrator/index.js'
221
214
  // say `128 + SIGKILL`; the decode is the runner's convention, one copy.
222
215
  export { exitSignal } from './orchestrator/index.js'
223
216
  export type {
224
- CacheSource,
225
217
  RunContextRecord,
226
218
  RunSummaryRecord,
227
219
  TaskLogBundle,
@@ -232,5 +224,5 @@ export type {
232
224
  TelemetrySink,
233
225
  } from './orchestrator/index.js'
234
226
 
235
- // The serializable run result and its per-task views (what `run()` returns).
236
- export type { RunResult, TaskView, OutcomeView } from './orchestrator/index.js'
227
+ // The serializable per-task views.
228
+ export type { TaskView, OutcomeView } from './report/index.js'
@@ -62,13 +62,15 @@ function wholeProjects(
62
62
  * `input`, a changed `file` is one of its declared inputs; `project`, its
63
63
  * project changed and the task reads it whole (an uncached task, a path no
64
64
  * cached task declares, or no single `file`: a lockfile claim, a manifest
65
- * edge, a config import); `package`, a `^` edge passes through `project`, a
65
+ * edge, a config import); `workspace`, a workspace-wide `file` (the
66
+ * workspace config, a root file a plugin claims) reached every task of its
67
+ * project; `package`, a `^` edge passes through `project`, a
66
68
  * changed package with no such task; `named`, asked as `pkg#task`;
67
69
  * `selected`, another filter selected its project outright. `via` is the
68
70
  * `dependsOn` chain, nearest first, to the task the change seeded.
69
71
  */
70
72
  export interface AffectedReason {
71
- kind: 'input' | 'project' | 'package' | 'named' | 'selected'
73
+ kind: 'input' | 'project' | 'workspace' | 'package' | 'named' | 'selected'
72
74
  /** Workspace-relative. */
73
75
  file?: string
74
76
  project?: string
@@ -102,7 +104,10 @@ export function affectedRoots(
102
104
  const seeds = explain === undefined ? null : new Map<string, AffectedReason>()
103
105
  const through = explain === undefined ? null : new Map<string, string>()
104
106
  const inProject = (name: string, rel: string | undefined): AffectedReason => {
105
- if (rel === undefined) return { kind: 'project' }
107
+ if (rel === undefined) {
108
+ const file = changes.wholeBy?.get(name)
109
+ return file === undefined ? { kind: 'project' } : { kind: 'workspace', file }
110
+ }
106
111
  const dir = path.relative(explain!.workspaceRoot, projects.get(name)!.dir)
107
112
  return { kind: 'project', file: dir === '' ? rel : path.posix.join(dir, rel) }
108
113
  }
@@ -30,7 +30,7 @@ import {
30
30
  resolveCacheDir,
31
31
  resolveStoreRoot,
32
32
  } from '../workspace/index.js'
33
- import { flakyTasks, type FlakyTask } from './failure-mode.js'
33
+ import { flakyTasks, type FlakyTask } from '../history/index.js'
34
34
  import type { VxPlugin } from './plugin.js'
35
35
  import { discoverProjects, loadProjects, loadWorkspacePlugins, lockReader } from './projects.js'
36
36
  import { atPath, envName, resolveSetting, SETTINGS, type SettingSource } from '../settings.js'
@@ -384,7 +384,7 @@ function gitVersion(): string | null {
384
384
  function settingsFact(config: WorkspaceConfig | null): InfoFacts['settings'] {
385
385
  const out: InfoFacts['settings'] = {}
386
386
  for (const s of SETTINGS) {
387
- const env = process.env[envName(s.path)]
387
+ const env = s.env === false ? undefined : process.env[envName(s.path)]
388
388
  const declared = s.config === false ? undefined : atPath(config, s.path)
389
389
  out[s.path] =
390
390
  env !== undefined && env !== ''
@@ -0,0 +1,80 @@
1
+ // What a dispatch hands executeTask: the run's half, built once, and the
2
+ // task's half, looked up per dispatch. The run's half is one fixed-shape
3
+ // object every task spreads; the task's optional fields are assigned only
4
+ // when present: twelve conditional spreads per task cost ~18 ms of object
5
+ // copying on a 1,090-package warm run (X-149).
6
+
7
+ import type { TaskNode, TaskOutcome } from '../graph/index.js'
8
+ import type { Logger } from '../report/index.js'
9
+ import type { DownloadMode } from './download-policy.js'
10
+ import type { ExecuteArgs } from './execute-task.js'
11
+ import type { ShortCircuit } from './local-shortcircuit.js'
12
+ import { UNPLACED_EXECUTOR, type Placements } from './placement.js'
13
+
14
+ type TaskField =
15
+ | 'node'
16
+ | 'upstream'
17
+ | 'log'
18
+ | 'executor'
19
+ | 'nestedProjectDirs'
20
+ | 'remoteOnlyNoop'
21
+ | 'remoteOnly'
22
+ | 'download'
23
+ | 'preProbed'
24
+ | 'upfrontKey'
25
+ | 'upfrontGroupKey'
26
+ | 'taintedUpstream'
27
+ | 'noDependants'
28
+ | 'terminal'
29
+
30
+ /** What every task of a run shares; an absent optional stays absent. */
31
+ export type RunScope = Omit<ExecuteArgs, TaskField>
32
+
33
+ /** Where the task's half is read from, per task id. */
34
+ export interface TaskFacts {
35
+ log: (node: TaskNode) => Logger
36
+ placements: Placements
37
+ nestedDirsByProject: ReadonlyMap<string, string[]>
38
+ downloadModeOf: ReadonlyMap<string, DownloadMode>
39
+ shortCircuit: Pick<ShortCircuit, 'preProbed' | 'uncachedKeys' | 'groupKeys'>
40
+ /** Tasks some other task depends on. */
41
+ dependedOn: ReadonlySet<string>
42
+ /** Tasks that hold the terminal (`terminalHolders`). */
43
+ holders: ReadonlySet<string>
44
+ }
45
+
46
+ /**
47
+ * The dispatch builder. `reuseProbe: false` takes the task's key again
48
+ * instead of the up-front probe (admission's retry after a dropped probe);
49
+ * `tainted` is the run's taint verdict for this dispatch.
50
+ */
51
+ export function executeArgsOf(
52
+ scope: RunScope,
53
+ facts: TaskFacts,
54
+ ): (node: TaskNode, upstream: TaskOutcome[], reuseProbe: boolean, tainted: boolean) => ExecuteArgs {
55
+ const { placements, shortCircuit } = facts
56
+ return (node, upstream, reuseProbe, tainted) => {
57
+ const id = node.id
58
+ const a: ExecuteArgs = {
59
+ ...scope,
60
+ node,
61
+ upstream,
62
+ log: facts.log(node),
63
+ executor: placements.executors.get(id) ?? UNPLACED_EXECUTOR,
64
+ nestedProjectDirs: facts.nestedDirsByProject.get(node.projectName) ?? [],
65
+ }
66
+ const probe = reuseProbe ? shortCircuit.preProbed.get(id) : undefined
67
+ const upfrontKey = shortCircuit.uncachedKeys.get(id)
68
+ const upfrontGroupKey = shortCircuit.groupKeys.get(id)
69
+ if (facts.downloadModeOf.get(id) === 'deferred') a.download = 'deferred'
70
+ if (placements.remoteOnlyNoop.has(id)) a.remoteOnlyNoop = true
71
+ if (placements.remoteOnly.has(id)) a.remoteOnly = true
72
+ if (probe !== undefined) a.preProbed = probe
73
+ if (upfrontKey !== undefined) a.upfrontKey = upfrontKey
74
+ if (upfrontGroupKey !== undefined) a.upfrontGroupKey = upfrontGroupKey
75
+ if (tainted) a.taintedUpstream = true
76
+ if (!facts.dependedOn.has(id)) a.noDependants = true
77
+ if (facts.holders.has(id)) a.terminal = true
78
+ return a
79
+ }
80
+ }