supercov 0.0.41 → 0.0.43

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 (35) hide show
  1. package/README.md +24 -4
  2. package/analyzers/typescript/README.md +55 -0
  3. package/analyzers/typescript/bin/compiler-identity.mjs +78 -0
  4. package/analyzers/typescript/bin/identity.mjs +67 -0
  5. package/analyzers/typescript/bin/query.mjs +29 -0
  6. package/analyzers/typescript/dist/analyze.js +3972 -0
  7. package/analyzers/typescript/dist/archive.js +309 -0
  8. package/analyzers/typescript/dist/build-identity.json +1 -0
  9. package/analyzers/typescript/dist/compiler.js +32 -0
  10. package/analyzers/typescript/dist/frontend.js +75 -0
  11. package/analyzers/typescript/dist/native-frontend.js +271 -0
  12. package/analyzers/typescript/dist/pragmas.js +143 -0
  13. package/analyzers/typescript/dist/types.js +1 -0
  14. package/analyzers/typescript/package.json +27 -0
  15. package/analyzers/typescript/src/analyze.ts +4538 -0
  16. package/analyzers/typescript/src/archive.ts +438 -0
  17. package/analyzers/typescript/src/compiler.ts +49 -0
  18. package/analyzers/typescript/src/frontend.ts +136 -0
  19. package/analyzers/typescript/src/native-frontend.ts +315 -0
  20. package/analyzers/typescript/src/pragmas.ts +218 -0
  21. package/analyzers/typescript/src/types.ts +45 -0
  22. package/analyzers/typescript/tsconfig.json +12 -0
  23. package/docs/agent-loop.md +13 -0
  24. package/docs/assertion-evidence.md +135 -0
  25. package/docs/cli.md +10 -0
  26. package/docs/code-verification.md +182 -0
  27. package/docs/supported-suites.md +44 -11
  28. package/docs/troubleshooting.md +13 -0
  29. package/docs/verification.md +12 -0
  30. package/package.json +33 -15
  31. package/runtime/javascript/jest.cjs +134 -0
  32. package/runtime/javascript/jest.config.mjs +39 -0
  33. package/runtime/javascript/jestReporter.mjs +77 -0
  34. package/runtime/javascript/register.mjs +22 -4
  35. package/runtime/javascript/runtime.mjs +51 -13
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ // Supercov Jest adapter. Jest loads it through `setupFilesAfterEnv` of the
3
+ // configuration the preload substitutes (jest.config.mjs), so it runs inside
4
+ // every test file's environment with Jest's globals in scope. It assigns each
5
+ // test attempt its exact scope before the test runs and writes the attempt's
6
+ // coverage and assertion phases after it. Jest tells the environment nothing
7
+ // about an outcome, so the reporter (jestReporter.mjs) records that; the two
8
+ // records share a test id and the report joins them. It computes no coverage.
9
+ //
10
+ // CommonJS on purpose: Jest's module system loads setup files without the
11
+ // ESM flag, and the runtime it needs is already on the sandbox global.
12
+ const { createHash } = require("node:crypto");
13
+ const { mkdirSync, renameSync, writeFileSync } = require("node:fs");
14
+ const { relative, resolve, sep } = require("node:path");
15
+
16
+ // jest-environment-node exposes the outer process's own globals to the sandbox
17
+ // through getters, so this is the same runtime instance the instrumented
18
+ // sources under test call into.
19
+ const runtime =
20
+ globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??
21
+ (typeof process !== "undefined" ? process.__SUPERCOV_DIRECT_RUNTIME__ : undefined);
22
+ const evidenceDirectory = process.env.SUPERCOV_EVIDENCE_DIR;
23
+
24
+ function digest(value) {
25
+ return createHash("sha256").update(value).digest("hex").slice(0, 24);
26
+ }
27
+ function localFile(path) {
28
+ return relative(process.cwd(), path).split(sep).join("/");
29
+ }
30
+ // Must match jestReporter.mjs: the reporter has the same file and full name.
31
+ function testIdentity(testFile, fullName) {
32
+ return `jest:${digest(`${testFile}\0${fullName}`)}`;
33
+ }
34
+ // Mirrors provenance.mjs, which is ESM and out of reach here.
35
+ const KINDS = [
36
+ ["unit", /(^|[/_.-])unit([/_.-]|$)/i],
37
+ ["component", /(^|[/_.-])(component|components|ct)([/_.-]|$)/i],
38
+ ["integration", /(^|[/_.-])(integration|int)([/_.-]|$)/i],
39
+ ["e2e", /(^|[/_.-])(e2e|end-to-end|offline|online)([/_.-]|$)/i],
40
+ ];
41
+ function provenance(testFile) {
42
+ const explicit = process.env.SUPERCOV_TEST_KIND?.trim();
43
+ if (explicit)
44
+ return { runner: "jest", kind: explicit.toLowerCase(), source: "explicit" };
45
+ const kind = KINDS.find(([, pattern]) => pattern.test(testFile))?.[0];
46
+ return kind
47
+ ? { runner: "jest", kind, source: "path" }
48
+ : { runner: "jest", kind: "unit", source: "runner-default" };
49
+ }
50
+ function writeEvidence(suffix, payload) {
51
+ const directory = resolve(process.cwd(), evidenceDirectory, suffix);
52
+ mkdirSync(directory, { recursive: true });
53
+ const target = resolve(directory, "mcdc.json");
54
+ const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
55
+ writeFileSync(temporary, `${JSON.stringify(payload)}\n`);
56
+ renameSync(temporary, target);
57
+ }
58
+
59
+ if (runtime && evidenceDirectory && typeof beforeEach === "function" && typeof afterEach === "function") {
60
+ // One in-memory snapshot per test; the server JSONL transport would be
61
+ // redundant and unattributed, as under Vitest.
62
+ runtime.enableRuntimeSnapshotEvidence();
63
+ const attempts = new Map();
64
+ const emittedSetupFiles = new Set();
65
+ let active;
66
+ beforeEach(() => {
67
+ const state = expect.getState();
68
+ const testFile = localFile(state.testPath ?? "unknown");
69
+ if (!emittedSetupFiles.has(testFile)) {
70
+ // Code the test file ran while loading, before its first test.
71
+ emittedSetupFiles.add(testFile);
72
+ const setupSnapshot = runtime.coverageSnapshot();
73
+ if (setupSnapshot.hits.length || setupSnapshot.decisions.length) {
74
+ writeEvidence(`jest-${digest(testFile)}-setup`, {
75
+ testId: `jest:${digest(testFile)}:setup`,
76
+ test: `${testFile} > module setup`,
77
+ testFile,
78
+ title: "module setup",
79
+ retry: 0,
80
+ status: "passed",
81
+ provenance: provenance(testFile),
82
+ role: "setup",
83
+ runtime: [setupSnapshot],
84
+ browser: [],
85
+ server: [],
86
+ });
87
+ }
88
+ }
89
+ const fullName = state.currentTestName ?? "test";
90
+ const testId = testIdentity(testFile, fullName);
91
+ const retry = attempts.get(testId) ?? 0;
92
+ attempts.set(testId, retry + 1);
93
+ const testKey = digest(testId);
94
+ const scope = {
95
+ version: 1,
96
+ runId: process.env.SUPERCOV_RUN_ID ?? "unscoped",
97
+ workerId: `jest-${process.env.JEST_WORKER_ID ?? process.pid}`,
98
+ testId,
99
+ testKey,
100
+ retry,
101
+ attemptId: `${testKey}-${retry}`,
102
+ };
103
+ active = { scope, testFile, fullName, retry };
104
+ runtime.activateCoverageScope(scope);
105
+ runtime.resetCoverage(testId);
106
+ });
107
+ afterEach(() => {
108
+ const current = active;
109
+ active = undefined;
110
+ if (!current)
111
+ return;
112
+ const { scope } = current;
113
+ writeEvidence(`jest-${scope.attemptId}`, {
114
+ testId: scope.testId,
115
+ scope,
116
+ test: current.fullName,
117
+ testFile: current.testFile,
118
+ title: current.fullName,
119
+ retry: current.retry,
120
+ // The reporter's record carries the outcome.
121
+ status: "unknown",
122
+ provenance: provenance(current.testFile),
123
+ phases: runtime.takeNodeAssertionPhases(scope),
124
+ runtime: [runtime.coverageSnapshot()],
125
+ browser: [],
126
+ server: [],
127
+ });
128
+ // One runtime state serves every test file a worker runs, so what this
129
+ // test recorded must not leak into the next file's module-setup
130
+ // snapshot (Vitest isolates files; Jest does not).
131
+ runtime.resetCoverage();
132
+ runtime.activateCoverageScope();
133
+ });
134
+ }
@@ -0,0 +1,39 @@
1
+ // Supercov's Jest configuration, handed to Jest through `--config` by the
2
+ // preload (register.mjs) in place of the user's. It reads the user's own
3
+ // configuration the way Jest reads it -- a jest.config.* file in any format
4
+ // Jest accepts, or the "jest" key of package.json -- and adds the adapter
5
+ // (jest.cjs) and the reporter (jestReporter.mjs). Nothing of the user's is
6
+ // replaced: their setup files and reporters keep running.
7
+ import { createRequire } from "node:module";
8
+ import { dirname, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const here = (name) => fileURLToPath(new URL(`./${name}`, import.meta.url));
12
+ const asList = (value) => (value === undefined ? [] : Array.isArray(value) ? value : [value]);
13
+
14
+ export default async function supercovJestConfig() {
15
+ let config = { rootDir: process.cwd() };
16
+ try {
17
+ // The project's own jest-config, so version and resolution match Jest's.
18
+ const projectRequire = createRequire(resolve(process.cwd(), "package.json"));
19
+ const { readInitialOptions } = projectRequire("jest-config");
20
+ const original = process.env["SUPERCOV_ORIGINAL_JEST_CONFIG"] || undefined;
21
+ const read = await readInitialOptions(original, { skipMultipleConfigError: true });
22
+ config = read.config ?? config;
23
+ config.rootDir ??= read.configPath ? dirname(read.configPath) : process.cwd();
24
+ } catch (error) {
25
+ // jest-config before 29.3 has no readInitialOptions; the project's
26
+ // configuration is not read and Jest's defaults apply.
27
+ process.emitWarning(`[supercov] the project's Jest configuration was not read (${error?.message ?? error}); Jest's defaults apply`);
28
+ }
29
+ return {
30
+ ...config,
31
+ // Concurrent tests in one file would share the worker's scope.
32
+ maxConcurrency: 1,
33
+ setupFilesAfterEnv: [...asList(config.setupFilesAfterEnv), here("jest.cjs")],
34
+ reporters: [
35
+ ...(config.reporters === undefined ? ["default"] : asList(config.reporters)),
36
+ here("jestReporter.mjs"),
37
+ ],
38
+ };
39
+ }
@@ -0,0 +1,77 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdirSync } from "node:fs";
3
+ import { relative, resolve, sep } from "node:path";
4
+ import { inferTestProvenance } from "./provenance.mjs";
5
+ import { atomicWriteFileSync } from "./atomic.mjs";
6
+ function digest(value) {
7
+ return createHash("sha256").update(value).digest("hex").slice(0, 24);
8
+ }
9
+ function localFile(path) {
10
+ return relative(process.cwd(), path).split(sep).join("/");
11
+ }
12
+ function attemptStatus(status) {
13
+ switch (status) {
14
+ case "passed":
15
+ case "failed":
16
+ return status;
17
+ case "pending":
18
+ case "skipped":
19
+ case "todo":
20
+ case "disabled":
21
+ return "skipped";
22
+ default:
23
+ return "unknown";
24
+ }
25
+ }
26
+ /**
27
+ * Records each Jest attempt's outcome, which the test environment never
28
+ * learns: a test that times out, one skipped before its hooks, one that
29
+ * failed after its last hook. The test id formula must match jest.cjs, whose
30
+ * record carries the attempt's coverage; the report joins the two.
31
+ */
32
+ export default class SupercovJestReporter {
33
+ onTestCaseResult(test, result) {
34
+ const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"];
35
+ if (!evidenceDirectory)
36
+ return;
37
+ const testFile = localFile(test.path);
38
+ const testId = `jest:${digest(`${testFile}\0${result.fullName}`)}`;
39
+ const retry = Math.max((result.invocations ?? 1) - 1, 0);
40
+ const status = attemptStatus(result.status);
41
+ const provenance = inferTestProvenance({
42
+ runner: "jest",
43
+ file: testFile,
44
+ explicitKind: process.env["SUPERCOV_TEST_KIND"],
45
+ });
46
+ const record = (attempt, attemptStatus, flaky) => {
47
+ const payload = {
48
+ testId,
49
+ test: result.fullName,
50
+ testFile,
51
+ title: result.title,
52
+ retry: attempt,
53
+ status: attemptStatus,
54
+ expectedStatus: "passed",
55
+ flaky,
56
+ provenance,
57
+ runtime: [],
58
+ browser: [],
59
+ server: [],
60
+ };
61
+ const directory = resolve(process.cwd(), evidenceDirectory, `jest-${digest(testId)}-${attempt}-status`);
62
+ mkdirSync(directory, { recursive: true });
63
+ atomicWriteFileSync(resolve(directory, "mcdc.json"), `${JSON.stringify(payload)}\n`);
64
+ };
65
+ // `jest.retryTimes` reports one result after the last attempt; every
66
+ // attempt before it failed, or there would have been no retry. The
67
+ // adapter wrote each attempt's coverage without an outcome.
68
+ for (let attempt = 0; attempt < retry; attempt += 1)
69
+ record(attempt, "failed", false);
70
+ record(retry, status, retry > 0 && status === "passed");
71
+ }
72
+ // Jest calls these on every reporter.
73
+ onRunComplete() { }
74
+ getLastError() {
75
+ return undefined;
76
+ }
77
+ }
@@ -9,6 +9,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
9
9
  import Module, { register, syncBuiltinESMExports } from "node:module";
10
10
  import { closeSync, openSync, unlinkSync } from "node:fs";
11
11
  import { resolve } from "node:path";
12
+ import { fileURLToPath } from "node:url";
12
13
  import { installLaunchSupervisor, wrapImportedCapability } from "./launchSupervisor.mjs";
13
14
  import { __supercovBindCapabilityWrapper } from "./capability.mjs";
14
15
  installLaunchSupervisor();
@@ -115,7 +116,6 @@ syncBuiltinESMExports();
115
116
  // without editing package scripts, Vitest configs, setup files, or test imports.
116
117
  const generatedVitestConfig = process.env.SUPERCOV_GENERATED_VITEST_CONFIG;
117
118
  const generatedPlaywrightConfig = process.env.SUPERCOV_GENERATED_PLAYWRIGHT_CONFIG;
118
- const generatedJestConfig = process.env.SUPERCOV_GENERATED_JEST_CONFIG;
119
119
  const entrypoint = process.argv[1]?.replaceAll("\\", "/") ?? "";
120
120
  const playwrightTarget = process.env.SUPERCOV_PLAYWRIGHT_MODULE;
121
121
  const projectRoot = process.env.SUPERCOV_PROJECT_ROOT?.replaceAll("\\", "/").replace(/\/$/, "");
@@ -171,19 +171,37 @@ if (generatedVitestConfig && /\/vitest(?:\.m?js)?$/.test(entrypoint)) {
171
171
  });
172
172
  }
173
173
  }
174
- if (generatedJestConfig && isJestEntrypoint) {
174
+ if ((isJestEntrypoint || process.env.JEST_WORKER_ID) && process.env.SUPERCOV_EVIDENCE_DIR) {
175
+ // Jest evaluates instrumented modules in its own module system, past the
176
+ // loader hook that imports the runtime on first use elsewhere, and
177
+ // jest-environment-node exposes to each test's sandbox only the globals
178
+ // the outer process held when that class loaded. The Jest process and
179
+ // each of its workers (JEST_WORKER_ID) therefore load the runtime first;
180
+ // without this, a suite with more than one test file failed on every
181
+ // instrumented module's first line inside the workers.
182
+ globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.mjs");
183
+ process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
184
+ }
185
+ if (isJestEntrypoint && process.env.SUPERCOV_EVIDENCE_DIR) {
186
+ // Jest reads one configuration. Ours (jest.config.mjs) reads the user's
187
+ // the way Jest would and adds the adapter and reporter; an explicit
188
+ // --config on the command line reaches it through the environment.
175
189
  for (let index = 2; index < process.argv.length; index += 1) {
176
190
  const argument = process.argv[index];
177
191
  if (argument === "--config" || argument === "-c") {
178
- process.argv.splice(index, process.argv[index + 1] ? 2 : 1);
192
+ const value = process.argv[index + 1];
193
+ if (value)
194
+ process.env.SUPERCOV_ORIGINAL_JEST_CONFIG = resolve(value);
195
+ process.argv.splice(index, value ? 2 : 1);
179
196
  index -= 1;
180
197
  }
181
198
  else if (argument?.startsWith("--config=")) {
199
+ process.env.SUPERCOV_ORIGINAL_JEST_CONFIG = resolve(argument.slice("--config=".length));
182
200
  process.argv.splice(index, 1);
183
201
  index -= 1;
184
202
  }
185
203
  }
186
- process.argv.push("--config", generatedJestConfig);
204
+ process.argv.push("--config", fileURLToPath(new URL("./jest.config.mjs", import.meta.url)));
187
205
  }
188
206
  if (generatedPlaywrightConfig &&
189
207
  isPlaywrightEntrypoint) {
@@ -144,6 +144,12 @@ function createState() {
144
144
  decisions: /* @__PURE__ */ new Map(),
145
145
  hits: /* @__PURE__ */ new Set(),
146
146
  events: [],
147
+ // Position of the test-file statement currently executing ("file:line:column"), set by the
148
+ // instrumented test module; production hits recorded meanwhile are attributed to it.
149
+ testStatement: void 0,
150
+ // Per value-position logical expression (`a && b`, `a || b`, `a ?? b`): the distinct
151
+ // (right operand evaluated?, result truthy?) pairs seen, keyed by branch id.
152
+ logicals: /* @__PURE__ */ new Map(),
147
153
  eventKeys: /* @__PURE__ */ new Set(),
148
154
  bufferedAttempts: /* @__PURE__ */ new Set(),
149
155
  serverBuffers: /* @__PURE__ */ new Map(),
@@ -293,17 +299,26 @@ function decisionSnapshot() {
293
299
  vectors: [...decision.vectors.values()]
294
300
  }));
295
301
  }
302
+ function logicalSnapshot() {
303
+ return [...state.logicals.entries()].map(([id, vectors]) => ({
304
+ id,
305
+ vectors: [...vectors.values()]
306
+ }));
307
+ }
296
308
  function coverageSnapshot() {
297
309
  return {
298
310
  decisions: decisionSnapshot(),
299
311
  hits: [...state.hits],
300
- events: state.events
312
+ events: state.events,
313
+ logicals: logicalSnapshot()
301
314
  };
302
315
  }
303
316
  function resetCoverage(testId2) {
304
317
  state.decisions.clear();
305
318
  state.hits.clear();
306
319
  state.events.length = 0;
320
+ state.testStatement = void 0;
321
+ state.logicals.clear();
307
322
  state.eventKeys.clear();
308
323
  state.probeV2ContextEpochs.clear();
309
324
  activateProbeV2Key(`reset\0${state.probeV2NextEpoch}`, true);
@@ -604,21 +619,22 @@ function appendServer(record) {
604
619
  var _a8, _b, _c, _d;
605
620
  if (state.runtimeSnapshots) {
606
621
  const timestampMs = (_a8 = record.timestampMs) != null ? _a8 : Date.now();
622
+ const statement = record.statementId ? { statementId: record.statementId } : {};
607
623
  if (record.type === "decision") {
608
- recordBrowserEvent(__spreadProps(__spreadValues({
624
+ recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
609
625
  type: "decision",
610
626
  id: record.meta.id,
611
627
  vector: record.vector,
612
628
  timestampMs
613
- }, record.phaseId ? { phaseId: record.phaseId } : {}), {
629
+ }, record.phaseId ? { phaseId: record.phaseId } : {}), statement), {
614
630
  environment: "server"
615
631
  }));
616
632
  } else {
617
- recordBrowserEvent(__spreadProps(__spreadValues({
633
+ recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
618
634
  type: "hit",
619
635
  id: record.id,
620
636
  timestampMs
621
- }, record.phaseId ? { phaseId: record.phaseId } : {}), {
637
+ }, record.phaseId ? { phaseId: record.phaseId } : {}), statement), {
622
638
  environment: "server"
623
639
  }));
624
640
  }
@@ -962,25 +978,31 @@ function recordBrowserEvent(event) {
962
978
  state.events.push(event);
963
979
  return true;
964
980
  }
981
+ function testStatement(source) {
982
+ state.testStatement = typeof source === "string" && source.length > 0 ? source : void 0;
983
+ }
984
+ function statementFields() {
985
+ return state.testStatement ? { statementId: state.testStatement } : {};
986
+ }
965
987
  function coverageHit(id) {
966
988
  state.hits.add(id);
967
989
  const timestampMs = Date.now();
968
990
  const phaseId = currentPhaseId();
969
991
  if (isBrowser) {
970
- if (recordBrowserEvent(__spreadProps(__spreadValues({
992
+ if (recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
971
993
  type: "hit",
972
994
  id,
973
995
  timestampMs
974
- }, phaseId ? { phaseId } : {}), {
996
+ }, phaseId ? { phaseId } : {}), statementFields()), {
975
997
  environment: "browser"
976
998
  })))
977
999
  persistBrowser();
978
1000
  } else {
979
- appendServer(__spreadValues({
1001
+ appendServer(__spreadValues(__spreadValues({
980
1002
  type: "hit",
981
1003
  id,
982
1004
  timestampMs
983
- }, phaseId ? { phaseId } : {}));
1005
+ }, phaseId ? { phaseId } : {}), statementFields()));
984
1006
  }
985
1007
  }
986
1008
  function registerProbeV2(definition) {
@@ -1113,6 +1135,20 @@ function selectionRight(frame, value, inferredName) {
1113
1135
  }
1114
1136
  function selectionEnd(frame, value) {
1115
1137
  coverageHit(frame.rightEvaluated ? frame.rightId : frame.shortId);
1138
+ // Per-operand outcomes for the verifier: which side was selected and whether the result is
1139
+ // truthy. With the operator (known from the manifest) this gives each operand's outcome.
1140
+ const separator = frame.shortId.lastIndexOf(":");
1141
+ if (separator > 0) {
1142
+ const branchId = frame.shortId.slice(0, separator);
1143
+ const right = frame.rightEvaluated;
1144
+ const truthy = Boolean(value);
1145
+ let vectors = state.logicals.get(branchId);
1146
+ if (!vectors) {
1147
+ vectors = /* @__PURE__ */ new Map();
1148
+ state.logicals.set(branchId, vectors);
1149
+ }
1150
+ vectors.set(`${right ? "R" : "S"}${truthy ? "T" : "F"}`, { right, truthy });
1151
+ }
1116
1152
  return value;
1117
1153
  }
1118
1154
  function optionalSelect(shortId, continuedId, value) {
@@ -1201,22 +1237,22 @@ function mcdcEnd(frame, value) {
1201
1237
  const timestampMs = Date.now();
1202
1238
  const phaseId = currentPhaseId();
1203
1239
  if (isBrowser) {
1204
- if (recordBrowserEvent(__spreadProps(__spreadValues({
1240
+ if (recordBrowserEvent(__spreadProps(__spreadValues(__spreadValues({
1205
1241
  type: "decision",
1206
1242
  id: decision.meta.id,
1207
1243
  vector,
1208
1244
  timestampMs
1209
- }, phaseId ? { phaseId } : {}), {
1245
+ }, phaseId ? { phaseId } : {}), statementFields()), {
1210
1246
  environment: "browser"
1211
1247
  })))
1212
1248
  persistBrowser();
1213
1249
  } else {
1214
- appendServer(__spreadValues({
1250
+ appendServer(__spreadValues(__spreadValues({
1215
1251
  type: "decision",
1216
1252
  meta: decision.meta,
1217
1253
  vector,
1218
1254
  timestampMs
1219
- }, phaseId ? { phaseId } : {}));
1255
+ }, phaseId ? { phaseId } : {}), statementFields()));
1220
1256
  }
1221
1257
  return value;
1222
1258
  }
@@ -1257,6 +1293,7 @@ const directRuntimeApi = {
1257
1293
  selectionEnd,
1258
1294
  selectionRight,
1259
1295
  takeNodeAssertionPhases,
1296
+ testStatement,
1260
1297
  tryBegin,
1261
1298
  tryCatch,
1262
1299
  tryEnd,
@@ -1306,6 +1343,7 @@ export {
1306
1343
  selectionEnd,
1307
1344
  selectionRight,
1308
1345
  takeNodeAssertionPhases,
1346
+ testStatement,
1309
1347
  tryBegin,
1310
1348
  tryCatch,
1311
1349
  tryEnd,