@elench/testkit 0.1.100 → 0.1.101

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 CHANGED
@@ -43,9 +43,6 @@ npx @elench/testkit --workers 8
43
43
  # One file-level wall clock budget for every suite file
44
44
  npx @elench/testkit --file-timeout-seconds 60
45
45
 
46
- # Run a deterministic shard
47
- npx @elench/testkit --shard 1/3
48
-
49
46
  # Specific service / suite
50
47
  npx @elench/testkit --service frontend --type pw -s navigation
51
48
  npx @elench/testkit --service api --type int -s health
@@ -126,6 +123,12 @@ output, emitted artifacts, and assistant-visible run state are persisted under
126
123
  run counts, pass/fail/skip counts, average duration, and last observed status,
127
124
  and those summaries are exposed in compact, verbose, and JSON discovery output.
128
125
 
126
+ Test execution also maintains a scheduler cache at `.testkit/timings.json`.
127
+ Completed file-level task durations are used to rank future runs with a
128
+ longest-estimated-duration-first policy, so slow files start earlier when
129
+ workers are available. Run artifacts include compact scheduler metadata under
130
+ `planning` so ordering decisions are inspectable.
131
+
129
132
  ## Automatic Regression Diagnosis
130
133
 
131
134
  If `regressions.file` is configured, every run automatically classifies observed
package/lib/cli/args.mjs CHANGED
@@ -58,25 +58,6 @@ export {
58
58
  parseWorkersOption,
59
59
  };
60
60
 
61
- export function parseShardOption(value) {
62
- if (!value) return null;
63
-
64
- const match = String(value).match(/^(\d+)\/(\d+)$/);
65
- if (!match) {
66
- throw new Error(
67
- `Invalid --shard value "${value}". Expected the form "i/n", e.g. 1/3.`
68
- );
69
- }
70
-
71
- const index = Number.parseInt(match[1], 10);
72
- const total = Number.parseInt(match[2], 10);
73
- if (index <= 0 || total <= 0 || index > total) {
74
- throw new Error(`Invalid --shard value "${value}". Expected 1 <= i <= n.`);
75
- }
76
-
77
- return { index, total };
78
- }
79
-
80
61
  export function resolveRequestedFiles(fileNames, productDir, invocationCwd = process.cwd()) {
81
62
  const resolved = [];
82
63
  const seen = new Set();
@@ -1,9 +1,9 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { loadCurrentRunArtifact, loadLatestRunArtifact } from "../../results/artifacts.mjs";
4
3
 
5
4
  const POLL_INTERVAL_MS = 150;
6
5
  const OBSERVED_KINDS = new Set(["run", "discover", "status", "doctor", "typecheck"]);
6
+ const RUN_KINDS = new Set(["run", "int", "e2e", "scenario", "dal", "load", "pw", "all"]);
7
7
 
8
8
  export function createAssistantCommandObserver({
9
9
  productDir,
@@ -13,15 +13,16 @@ export function createAssistantCommandObserver({
13
13
  intervalMs = POLL_INTERVAL_MS,
14
14
  } = {}) {
15
15
  const seenResultFiles = new Set();
16
+ const seenCommandLogEvents = new Set();
17
+ const observedRunCommandIds = new Set();
16
18
  let timer = null;
17
19
  let running = false;
18
- let lastArtifactSignature = null;
19
- let preferLatestArtifact = false;
20
+ let lastArtifactSignatures = new Map();
20
21
 
21
22
  function start() {
22
23
  if (running) return;
23
24
  running = true;
24
- lastArtifactSignature = readArtifactSignature();
25
+ lastArtifactSignatures = readArtifactSignatures();
25
26
  scan();
26
27
  timer = setInterval(scan, intervalMs);
27
28
  }
@@ -34,10 +35,32 @@ export function createAssistantCommandObserver({
34
35
  }
35
36
 
36
37
  function scan() {
38
+ observeCommandLog();
37
39
  observeCommandResults();
38
40
  observeRunArtifact();
39
41
  }
40
42
 
43
+ function observeCommandLog() {
44
+ const commandLogPath = commandLog?.commandLogPath;
45
+ if (!commandLogPath || !fs.existsSync(commandLogPath)) return;
46
+ const lines = fs.readFileSync(commandLogPath, "utf8").split(/\r?\n/).filter(Boolean);
47
+ for (const [index, line] of lines.entries()) {
48
+ const eventKey = `${index}:${line}`;
49
+ if (seenCommandLogEvents.has(eventKey)) continue;
50
+ seenCommandLogEvents.add(eventKey);
51
+ let event = null;
52
+ try {
53
+ event = JSON.parse(line);
54
+ } catch {
55
+ continue;
56
+ }
57
+ if (event.sessionId && commandLog.sessionId && event.sessionId !== commandLog.sessionId) continue;
58
+ if (event.type === "command_start" && isRunCommand(event)) {
59
+ observedRunCommandIds.add(event.commandId);
60
+ }
61
+ }
62
+ }
63
+
41
64
  function observeCommandResults() {
42
65
  const resultDir = commandLog?.resultDir;
43
66
  if (!resultDir || !fs.existsSync(resultDir)) return;
@@ -54,10 +77,11 @@ export function createAssistantCommandObserver({
54
77
  } catch {
55
78
  continue;
56
79
  }
80
+ if (document.sessionId && commandLog.sessionId && document.sessionId !== commandLog.sessionId) continue;
57
81
  if (!OBSERVED_KINDS.has(document.kind)) continue;
58
82
  seenResultFiles.add(filePath);
59
83
  if (document.kind === "run") {
60
- preferLatestArtifact = true;
84
+ observedRunCommandIds.add(document.commandId);
61
85
  hydrateRunArtifact("command-result", document);
62
86
  }
63
87
  onEvent?.({
@@ -68,29 +92,34 @@ export function createAssistantCommandObserver({
68
92
  }
69
93
 
70
94
  function observeRunArtifact() {
71
- const signature = readArtifactSignature();
72
- if (!signature) return;
73
- if (signature === lastArtifactSignature) return;
74
- lastArtifactSignature = signature;
75
- hydrateRunArtifact("artifact", { artifactPath: signature.split(":")[0] });
76
- }
95
+ const signatures = readArtifactSignatures();
96
+ const changed = [...signatures.entries()]
97
+ .filter(([artifactPath, signature]) => lastArtifactSignatures.get(artifactPath) !== signature)
98
+ .map(([artifactPath, signature]) => ({
99
+ artifactPath,
100
+ signature,
101
+ mtimeMs: Number(signature.split(":").at(-2) || 0),
102
+ }))
103
+ .sort((left, right) => left.mtimeMs - right.mtimeMs);
104
+ lastArtifactSignatures = signatures;
77
105
 
78
- function readArtifactSignature() {
79
- const artifactPath = resolveObservableArtifactPath();
80
- if (!artifactPath) return null;
81
- const stat = fs.statSync(artifactPath);
82
- return `${artifactPath}:${stat.mtimeMs}:${stat.size}`;
106
+ for (const entry of changed) {
107
+ const artifact = readJsonFile(entry.artifactPath);
108
+ if (!artifact || !shouldHydrateObservedArtifact(artifact)) continue;
109
+ hydrateRunArtifact("artifact", { artifactPath: entry.artifactPath, artifact });
110
+ }
83
111
  }
84
112
 
85
- function resolveObservableArtifactPath() {
113
+ function readArtifactSignatures() {
86
114
  const livePath = path.join(productDir, ".testkit", "results", "live.json");
87
115
  const latestPath = path.join(productDir, ".testkit", "results", "latest.json");
88
- const liveStat = safeStat(livePath);
89
- const latestStat = safeStat(latestPath);
90
- if (preferLatestArtifact && latestStat) return latestPath;
91
- if (latestStat && (!liveStat || latestStat.mtimeMs >= liveStat.mtimeMs)) return latestPath;
92
- if (liveStat) return livePath;
93
- return null;
116
+ const signatures = new Map();
117
+ for (const artifactPath of [livePath, latestPath]) {
118
+ const stat = safeStat(artifactPath);
119
+ if (!stat) continue;
120
+ signatures.set(artifactPath, `${artifactPath}:${stat.mtimeMs}:${stat.size}`);
121
+ }
122
+ return signatures;
94
123
  }
95
124
 
96
125
  function hydrateRunArtifact(source, command = null) {
@@ -107,29 +136,31 @@ export function createAssistantCommandObserver({
107
136
 
108
137
  function loadObservedRunArtifact(command = null) {
109
138
  if (command?.result?.runArtifact) return command.result.runArtifact;
110
- if (command?.artifactPath && fs.existsSync(command.artifactPath)) {
111
- try {
112
- return JSON.parse(fs.readFileSync(command.artifactPath, "utf8"));
113
- } catch {
114
- return null;
115
- }
116
- }
117
- const artifactPath = resolveObservableArtifactPath();
118
- if (artifactPath) {
119
- try {
120
- return JSON.parse(fs.readFileSync(artifactPath, "utf8"));
121
- } catch {
122
- return null;
123
- }
124
- }
139
+ if (command?.artifact) return command.artifact;
140
+ if (command?.artifactPath && fs.existsSync(command.artifactPath)) return readJsonFile(command.artifactPath);
141
+ return null;
142
+ }
143
+
144
+ function shouldHydrateObservedArtifact(artifact) {
145
+ const assistant = artifact?.provenance?.assistant || {};
146
+ if (!assistant.sessionId || !assistant.commandId) return false;
147
+ if (commandLog?.sessionId && assistant.sessionId !== commandLog.sessionId) return false;
148
+ return observedRunCommandIds.has(assistant.commandId);
149
+ }
150
+
151
+ function isRunCommand(event) {
152
+ if (!event?.commandId) return false;
153
+ if (event.kind === "run") return true;
154
+ if (RUN_KINDS.has(event.kind)) return true;
155
+ const argv = Array.isArray(event.argv) ? event.argv : [];
156
+ return argv[0] === "run" || RUN_KINDS.has(argv[0]);
157
+ }
158
+
159
+ function readJsonFile(filePath) {
125
160
  try {
126
- return loadCurrentRunArtifact(productDir);
161
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
127
162
  } catch {
128
- try {
129
- return loadLatestRunArtifact(productDir);
130
- } catch {
131
- return null;
132
- }
163
+ return null;
133
164
  }
134
165
  }
135
166
 
@@ -7,6 +7,19 @@ export const ASSISTANT_COMMAND_LOG_ENV = "TESTKIT_ASSISTANT_COMMAND_LOG";
7
7
  export const ASSISTANT_COMMAND_ID_ENV = "TESTKIT_ASSISTANT_COMMAND_ID";
8
8
  export const ASSISTANT_WRAPPER_LOGGED_ENV = "TESTKIT_ASSISTANT_WRAPPER_LOGGED";
9
9
 
10
+ const RUN_SHORTCUTS = new Set(["int", "e2e", "scenario", "dal", "load", "pw", "all"]);
11
+ const FLAGS_WITH_VALUES = new Set([
12
+ "--dir",
13
+ "--service",
14
+ "--type",
15
+ "--suite",
16
+ "--file",
17
+ "--workers",
18
+ "--file-timeout-seconds",
19
+ "--seed",
20
+ "--output-mode",
21
+ ]);
22
+
10
23
  export function createAssistantCommandContext({
11
24
  kind,
12
25
  argv = process.argv.slice(2),
@@ -136,8 +149,22 @@ export function appendAssistantCommandLog(context, event) {
136
149
  }
137
150
 
138
151
  function inferCommandKind(argv) {
139
- const positionals = (Array.isArray(argv) ? argv : []).filter((arg) => !String(arg).startsWith("-"));
140
- return positionals[0] || "run";
152
+ const first = findFirstPositional(argv);
153
+ if (!first || RUN_SHORTCUTS.has(first)) return "run";
154
+ return first;
155
+ }
156
+
157
+ function findFirstPositional(argv) {
158
+ const args = Array.isArray(argv) ? argv : [];
159
+ for (let index = 0; index < args.length; index += 1) {
160
+ const arg = String(args[index]);
161
+ if (FLAGS_WITH_VALUES.has(arg)) {
162
+ index += 1;
163
+ continue;
164
+ }
165
+ if (!arg.startsWith("-")) return arg;
166
+ }
167
+ return null;
141
168
  }
142
169
 
143
170
  function inferExitCode(result) {
@@ -178,7 +178,27 @@ function appendCommandLog(event) {
178
178
  }
179
179
 
180
180
  function inferKind(args) {
181
- return args.find((arg) => !String(arg).startsWith("-")) || "run";
181
+ const runShortcuts = new Set(["int", "e2e", "scenario", "dal", "load", "pw", "all"]);
182
+ const flagsWithValues = new Set([
183
+ "--dir",
184
+ "--service",
185
+ "--type",
186
+ "--suite",
187
+ "--file",
188
+ "--workers",
189
+ "--file-timeout-seconds",
190
+ "--seed",
191
+ "--output-mode",
192
+ ]);
193
+ for (let index = 0; index < args.length; index += 1) {
194
+ const arg = String(args[index]);
195
+ if (flagsWithValues.has(arg)) {
196
+ index += 1;
197
+ continue;
198
+ }
199
+ if (!arg.startsWith("-")) return runShortcuts.has(arg) ? "run" : arg;
200
+ }
201
+ return "run";
182
202
  }
183
203
  `;
184
204
  }
@@ -19,7 +19,11 @@ export function loadAssistantSettings(productDir) {
19
19
  const filePath = assistantSettingsPath(productDir);
20
20
  try {
21
21
  const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
22
- return normalizeAssistantSettings(parsed);
22
+ const normalized = normalizeAssistantSettings(parsed);
23
+ if (JSON.stringify(parsed) !== JSON.stringify(normalized)) {
24
+ writeAssistantSettingsFile(filePath, normalized);
25
+ }
26
+ return normalized;
23
27
  } catch {
24
28
  return { ...DEFAULT_ASSISTANT_SETTINGS };
25
29
  }
@@ -27,8 +31,7 @@ export function loadAssistantSettings(productDir) {
27
31
 
28
32
  export function saveAssistantSettings(productDir, settings) {
29
33
  const filePath = assistantSettingsPath(productDir);
30
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
31
- fs.writeFileSync(filePath, `${JSON.stringify(normalizeAssistantSettings(settings), null, 2)}\n`);
34
+ writeAssistantSettingsFile(filePath, normalizeAssistantSettings(settings));
32
35
  }
33
36
 
34
37
  export function resetAssistantSettings(productDir) {
@@ -52,8 +55,9 @@ export function mergeAssistantSettings(...settingsObjects) {
52
55
 
53
56
  export function normalizeAssistantSettings(value = {}) {
54
57
  const provider = normalizeProvider(value.provider);
55
- const effort = normalizeEffort(value.effort);
56
- const model = normalizeOptionalString(value.model);
58
+ const extracted = extractEffortFromModel(value.model);
59
+ const effort = normalizeEffort(value.effort || extracted.effort);
60
+ const model = extracted.model;
57
61
  const providerArgs = Array.isArray(value.providerArgs)
58
62
  ? value.providerArgs.map((entry) => normalizeOptionalString(entry)).filter(Boolean)
59
63
  : [];
@@ -89,6 +93,19 @@ export function normalizeOptionalString(value) {
89
93
  return stringValue || null;
90
94
  }
91
95
 
96
+ export function extractEffortFromModel(value) {
97
+ const model = normalizeOptionalString(value);
98
+ if (!model) return { model: null, effort: null };
99
+ const tokens = model.split(/\s+/);
100
+ if (tokens.length < 2) return { model, effort: null };
101
+ const trailingEffort = tokens.at(-1);
102
+ if (!ASSISTANT_EFFORTS.includes(trailingEffort)) return { model, effort: null };
103
+ return {
104
+ model: tokens.slice(0, -1).join(" ").trim() || null,
105
+ effort: trailingEffort,
106
+ };
107
+ }
108
+
92
109
  function dropNullishSettings(settings) {
93
110
  const result = {};
94
111
  for (const [key, value] of Object.entries(settings)) {
@@ -96,3 +113,8 @@ function dropNullishSettings(settings) {
96
113
  }
97
114
  return result;
98
115
  }
116
+
117
+ function writeAssistantSettingsFile(filePath, settings) {
118
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
119
+ fs.writeFileSync(filePath, `${JSON.stringify(settings, null, 2)}\n`);
120
+ }
@@ -470,7 +470,13 @@ async function executeSlashCommand({
470
470
  }
471
471
  if (slash.type === "model") {
472
472
  state.setModel(slash.model, { custom: slash.custom });
473
- appendMessage({ role: "assistant", text: slash.model ? `Model set to ${slash.model}.` : "Model reset to provider default." });
473
+ const snapshot = state.getSnapshot();
474
+ appendMessage({
475
+ role: "assistant",
476
+ text: slash.model
477
+ ? `Model set to ${snapshot.model}${snapshot.effort ? ` with ${snapshot.effort} effort` : ""}.`
478
+ : "Model reset to provider default.",
479
+ });
474
480
  return;
475
481
  }
476
482
  if (slash.type === "model-list") {
@@ -32,9 +32,6 @@ export const runFlags = {
32
32
  "file-timeout-seconds": Flags.string({
33
33
  description: "Per-file wall-clock timeout in seconds",
34
34
  }),
35
- shard: Flags.string({
36
- description: "Run only shard i of n at suite granularity",
37
- }),
38
35
  seed: Flags.string({
39
36
  description: "Deterministic seed for scenario suites",
40
37
  }),
@@ -23,7 +23,6 @@ export function normalizeCliArgs(argv) {
23
23
  "--file",
24
24
  "--workers",
25
25
  "--file-timeout-seconds",
26
- "--shard",
27
26
  "--seed",
28
27
  "--input",
29
28
  "--output",
@@ -54,7 +53,6 @@ export function normalizeCliArgs(argv) {
54
53
  "-f",
55
54
  "--workers",
56
55
  "--file-timeout-seconds",
57
- "--shard",
58
56
  "--seed",
59
57
  "--write-status",
60
58
  "--allow-partial-status",
@@ -2,7 +2,6 @@ import * as runner from "../../../runner/index.mjs";
2
2
  import { loadManagedConfigs } from "../../../app/configs.mjs";
3
3
  import {
4
4
  parseFileTimeoutOption,
5
- parseShardOption,
6
5
  parseSuiteOption,
7
6
  parseTypeOption,
8
7
  parseWorkersOption,
@@ -25,7 +24,6 @@ export async function buildRunRequest(flags, positionalType = null, cwd = proces
25
24
  flags["file-timeout-seconds"] == null
26
25
  ? null
27
26
  : parseFileTimeoutOption(flags["file-timeout-seconds"]);
28
- const shard = parseShardOption(flags.shard);
29
27
  const typeValues = parseTypeOption(flags.type, positionalType);
30
28
  const suiteSelectors = parseSuiteOption(flags.suite);
31
29
  const rawFileNames = Array.isArray(flags.file) ? flags.file : [flags.file].filter(Boolean);
@@ -44,7 +42,6 @@ export async function buildRunRequest(flags, positionalType = null, cwd = proces
44
42
  fileNames,
45
43
  workers,
46
44
  fileTimeoutSeconds,
47
- shard,
48
45
  scenarioSeed: flags.seed || null,
49
46
  serviceFilter: flags.service || null,
50
47
  writeStatus: flags["write-status"],
@@ -4,12 +4,14 @@ import { writeLiveRunArtifact } from "./artifacts.mjs";
4
4
 
5
5
  export function createLiveSnapshotWriter({
6
6
  productDir,
7
+ runId,
7
8
  configs,
8
9
  trackers,
9
10
  startedAt,
10
11
  execution,
11
12
  workerState,
12
13
  selection,
14
+ provenance = null,
13
15
  metadata,
14
16
  logRegistry,
15
17
  setupRegistry,
@@ -21,6 +23,7 @@ export function createLiveSnapshotWriter({
21
23
  productDir,
22
24
  buildLiveRunArtifact({
23
25
  productDir,
26
+ runId,
24
27
  results: partialResults,
25
28
  startedAt,
26
29
  updatedAt: now,
@@ -31,9 +34,10 @@ export function createLiveSnapshotWriter({
31
34
  typeValues: selection.typeValues,
32
35
  suiteSelectors: selection.suiteSelectors,
33
36
  fileNames: selection.fileNames,
34
- shard: selection.shard,
35
37
  serviceFilter: selection.serviceFilter,
36
38
  scenarioSeed: selection.scenarioSeed,
39
+ planning: selection.planning || null,
40
+ provenance,
37
41
  metadata,
38
42
  summarizeDbBackend,
39
43
  serviceLogs: logRegistry.listServiceLogs(),
@@ -1,11 +1,10 @@
1
1
  import {
2
- applyShard,
3
2
  buildRuntimeGraphs,
4
- buildTaskQueue,
5
3
  claimNextTask,
6
4
  collectSuites,
7
5
  resolveRuntimeConfigs,
8
6
  } from "./planning.mjs";
7
+ import { buildRunPlanningMetadata, buildScheduledQueue } from "./scheduler/index.mjs";
9
8
  import {
10
9
  addTrackerError,
11
10
  buildServiceTrackers,
@@ -20,6 +19,7 @@ import {
20
19
  resetResultArtifacts,
21
20
  saveTimings,
22
21
  } from "./artifacts.mjs";
22
+ import { loadHistory } from "../history/index.mjs";
23
23
  import { createRunLogRegistry } from "./logs.mjs";
24
24
  import { createSetupOperationRegistry } from "./setup-operations.mjs";
25
25
  import {
@@ -40,12 +40,14 @@ import { createWorker, runWorker } from "./worker-loop.mjs";
40
40
  import { ensureRequestedFilesMatch, ensureStatusWriteAllowed } from "./run-guards.mjs";
41
41
  import { createLiveSnapshotWriter } from "./live-run.mjs";
42
42
  import { finalizeRunArtifacts } from "./run-finalization.mjs";
43
+ import { buildRunProvenance } from "./provenance.mjs";
43
44
 
44
45
  export async function runAll(configs, typeValues, suiteSelectors, opts, allConfigs = configs) {
45
46
  const configMap = new Map(allConfigs.map((config) => [config.name, config]));
46
47
  const startedAt = Date.now();
47
48
  const telemetry = configs[0]?.telemetry || null;
48
49
  const productDir = configs[0]?.productDir || process.cwd();
50
+ const provenance = buildRunProvenance(opts.env || process.env);
49
51
  await cleanupStaleRuns(productDir);
50
52
  resetResultArtifacts(productDir);
51
53
  const metadata = {
@@ -87,30 +89,33 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
87
89
  );
88
90
  reporter?.setServicePlans?.(servicePlans);
89
91
  const trackers = buildServiceTrackers(servicePlans, startedAt);
92
+ const runSelection = {
93
+ typeValues,
94
+ suiteSelectors,
95
+ fileNames: requestedFiles,
96
+ serviceFilter: opts.serviceFilter || null,
97
+ scenarioSeed: opts.scenarioSeed || null,
98
+ planning: null,
99
+ };
90
100
  let writeLiveSnapshot = () => {};
91
101
  const setupRegistry = createSetupOperationRegistry({ logRegistry, onChange: () => writeLiveSnapshot() });
102
+ const executedPlans = servicePlans.filter((plan) => !plan.skipped);
103
+ let exitCode = 0;
104
+ const lifecycle = createRunLifecycle(productDir);
92
105
  writeLiveSnapshot = createLiveSnapshotWriter({
93
106
  productDir,
107
+ runId: lifecycle.runId,
94
108
  configs,
95
109
  trackers,
96
110
  startedAt,
97
111
  execution,
98
112
  workerState,
99
- selection: {
100
- typeValues,
101
- suiteSelectors,
102
- fileNames: requestedFiles,
103
- shard: opts.shard || null,
104
- serviceFilter: opts.serviceFilter || null,
105
- scenarioSeed: opts.scenarioSeed || null,
106
- },
113
+ selection: runSelection,
114
+ provenance,
107
115
  metadata,
108
116
  logRegistry,
109
117
  setupRegistry,
110
118
  });
111
- const executedPlans = servicePlans.filter((plan) => !plan.skipped);
112
- let exitCode = 0;
113
- const lifecycle = createRunLifecycle(productDir);
114
119
  lifecycle.markRunning();
115
120
  lifecycle.installSignalHandlers();
116
121
  let results = [];
@@ -120,8 +125,11 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
120
125
  try {
121
126
  if (executedPlans.length > 0) {
122
127
  const timings = loadTimings(productDir);
128
+ const history = loadHistory(productDir);
123
129
  const graphs = buildRuntimeGraphs(executedPlans);
124
- const queue = buildTaskQueue(executedPlans, graphs, timings);
130
+ const queue = buildScheduledQueue(executedPlans, graphs, { timings, history });
131
+ runSelection.planning = buildRunPlanningMetadata(queue);
132
+ writeLiveSnapshot();
125
133
  reporter?.setTotalFileCount?.(queue.length);
126
134
  for (const task of queue) {
127
135
  task.scenarioSeed = opts.scenarioSeed || null;
@@ -191,6 +199,7 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
191
199
  );
192
200
  const finalized = await finalizeRunArtifacts({
193
201
  productDir,
202
+ runId: lifecycle.runId,
194
203
  results,
195
204
  startedAt,
196
205
  finishedAt,
@@ -198,14 +207,8 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
198
207
  workerCount: workerState.workerCount,
199
208
  runtimeInstanceCount: workerState.runtimeInstanceCount,
200
209
  runtimeStats: workerState.runtimeStats,
201
- selection: {
202
- typeValues,
203
- suiteSelectors,
204
- fileNames: requestedFiles,
205
- shard: opts.shard || null,
206
- serviceFilter: opts.serviceFilter || null,
207
- scenarioSeed: opts.scenarioSeed || null,
208
- },
210
+ selection: runSelection,
211
+ provenance,
209
212
  metadata,
210
213
  logRegistry,
211
214
  setupRegistry,
@@ -245,10 +248,7 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
245
248
 
246
249
  function collectServicePlans(configs, configMap, typeValues, suiteSelectors, opts, execution, reporter) {
247
250
  return configs.map((config) => {
248
- const suites = applyShard(
249
- collectSuites(config, typeValues, suiteSelectors, opts.fileNames || [], opts),
250
- opts.shard
251
- );
251
+ const suites = collectSuites(config, typeValues, suiteSelectors, opts.fileNames || [], opts);
252
252
 
253
253
  if (suites.length === 0) {
254
254
  reporter?.serviceSkipped?.(