@quikit/quikpilot-runner 8.2.5 → 8.2.6

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
@@ -1,27 +1,27 @@
1
- # @quikit/quikpilot-runner
2
-
3
- QuikPilot's CI runner. Invoked automatically by the `.github/workflows/quikpilot.yml` file QuikPilot
4
- installs in an onboarded repository — it fetches the run's work packet, runs your setup/lint/test
5
- commands, reports results to QuikTest, enforces the denied-path guardrail, commits, pushes, and
6
- reports back to QuikTrack.
7
-
8
- This package is not meant to be installed or run by hand. It exists so a fix to QuikPilot's runner
9
- logic ships as a new package version rather than a new pull request into every onboarded repository
10
- — the workflow file itself lives in your repository (QuikIT cannot update it directly), so it stays a
11
- thin, ~25-line launcher that invokes an exact, pinned version of this package via `npx`.
12
-
13
- It carries no secrets and needs none of your credentials: it authenticates to QuikTrack and to GitHub
14
- using short-lived tokens exchanged at runtime, never a value baked into the workflow file or this
15
- package.
16
-
17
- ## Versioning and support
18
-
19
- Every workflow file pins an **exact** version of this package (`npx --yes
20
- @quikit/quikpilot-runner@<version>`, never a range or `latest`) — a repository never picks up a new
21
- release on its own. Older published versions are not removed from npm and keep working as long as a
22
- repository's workflow file keeps pinning them; upgrading to a newer version only happens through a new
23
- pull request into that repository (QuikTrack's "re-bootstrap" flow), never silently.
24
-
25
- Releases follow [SemVer](https://semver.org/): a major version bump means the workflow file's own
26
- shape changed (inputs, permissions) and requires a new pull request regardless of this package's
27
- version; minor/patch releases are runner-logic fixes safe to pick up on the next re-bootstrap.
1
+ # @quikit/quikpilot-runner
2
+
3
+ QuikPilot's CI runner. Invoked automatically by the `.github/workflows/quikpilot.yml` file QuikPilot
4
+ installs in an onboarded repository — it fetches the run's work packet, runs your setup/lint/test
5
+ commands, reports results to QuikTest, enforces the denied-path guardrail, commits, pushes, and
6
+ reports back to QuikTrack.
7
+
8
+ This package is not meant to be installed or run by hand. It exists so a fix to QuikPilot's runner
9
+ logic ships as a new package version rather than a new pull request into every onboarded repository
10
+ — the workflow file itself lives in your repository (QuikIT cannot update it directly), so it stays a
11
+ thin, ~25-line launcher that invokes an exact, pinned version of this package via `npx`.
12
+
13
+ It carries no secrets and needs none of your credentials: it authenticates to QuikTrack and to GitHub
14
+ using short-lived tokens exchanged at runtime, never a value baked into the workflow file or this
15
+ package.
16
+
17
+ ## Versioning and support
18
+
19
+ Every workflow file pins an **exact** version of this package (`npx --yes
20
+ @quikit/quikpilot-runner@<version>`, never a range or `latest`) — a repository never picks up a new
21
+ release on its own. Older published versions are not removed from npm and keep working as long as a
22
+ repository's workflow file keeps pinning them; upgrading to a newer version only happens through a new
23
+ pull request into that repository (QuikTrack's "re-bootstrap" flow), never silently.
24
+
25
+ Releases follow [SemVer](https://semver.org/): a major version bump means the workflow file's own
26
+ shape changed (inputs, permissions) and requires a new pull request regardless of this package's
27
+ version; minor/patch releases are runner-logic fixes safe to pick up on the next re-bootstrap.
package/dist/index.js CHANGED
@@ -73,11 +73,12 @@ async function describeQuikTrackFailure(res) {
73
73
 
74
74
  // src/callback.ts
75
75
  async function reportBack(params) {
76
- const { callbackUrl, runToken, status, branch, warnings, targeting } = params;
76
+ const { callbackUrl, runToken, status, branch, warnings, targeting, baselineFindings } = params;
77
77
  const body = { status };
78
78
  if (branch) body.branch = branch;
79
79
  if (warnings && warnings.length > 0) body.warnings = warnings;
80
80
  if (targeting) body.targeting = targeting;
81
+ if (baselineFindings) body.baselineFindings = baselineFindings;
81
82
  const res = await fetch(callbackUrl, {
82
83
  method: "POST",
83
84
  headers: { Authorization: `Bearer ${runToken}`, "Content-Type": "application/json" },
@@ -109,10 +110,36 @@ function runTestCommand(command) {
109
110
  }
110
111
  function runTestCommandAsync(command) {
111
112
  if (!command) return Promise.resolve("success");
112
- return new Promise((resolve3) => {
113
+ return new Promise((resolve4) => {
113
114
  const child = (0, import_node_child_process.spawn)(command, { shell: "/bin/bash", stdio: "inherit" });
114
- child.on("exit", (code) => resolve3(code === 0 ? "success" : "failure"));
115
- child.on("error", () => resolve3("failure"));
115
+ child.on("exit", (code) => resolve4(code === 0 ? "success" : "failure"));
116
+ child.on("error", () => resolve4("failure"));
117
+ });
118
+ }
119
+ function runCommandWithTimeout(command, timeoutMs) {
120
+ return new Promise((resolve4) => {
121
+ let settled = false;
122
+ const finish = (outcome) => {
123
+ if (settled) return;
124
+ settled = true;
125
+ clearTimeout(timer);
126
+ resolve4(outcome);
127
+ };
128
+ const child = (0, import_node_child_process.spawn)(command, { shell: "/bin/bash", stdio: "inherit", detached: true });
129
+ const timer = setTimeout(() => {
130
+ try {
131
+ if (child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
132
+ else child.kill("SIGKILL");
133
+ } catch {
134
+ try {
135
+ child.kill("SIGKILL");
136
+ } catch {
137
+ }
138
+ }
139
+ finish("timeout");
140
+ }, timeoutMs);
141
+ child.on("exit", () => finish("exited"));
142
+ child.on("error", () => finish("error"));
116
143
  });
117
144
  }
118
145
 
@@ -959,8 +986,8 @@ async function reportToQuikTest(params) {
959
986
  try {
960
987
  console.log(`reporting ${report}`);
961
988
  const ciUrl = buildCiUrl(githubServerUrl, githubRepository, githubRunId);
962
- const { readFileSync: readFileSync5 } = await import("node:fs");
963
- const body = readFileSync5(report);
989
+ const { readFileSync: readFileSync6 } = await import("node:fs");
990
+ const body = readFileSync6(report);
964
991
  let url = `${resultsUrl}?ciUrl=${encodeURIComponent(ciUrl)}&automationIdFormat=bracket`;
965
992
  if (generatedFiles) {
966
993
  url += `&generatedFiles=${encodeURIComponent(generatedFiles.join(","))}`;
@@ -987,12 +1014,18 @@ async function reportToQuikTest(params) {
987
1014
  }
988
1015
  }
989
1016
 
1017
+ // src/baselineCheck.ts
1018
+ var import_node_child_process3 = require("node:child_process");
1019
+ var import_node_fs6 = require("node:fs");
1020
+ var import_node_path4 = require("node:path");
1021
+
990
1022
  // src/regressionCheck.ts
991
1023
  var import_node_fs5 = require("node:fs");
992
1024
  var TESTCASE_RE = /<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase>)/g;
993
1025
  var NAME_ATTR_RE = /\bname="([^"]*)"/;
994
1026
  var CLASSNAME_ATTR_RE = /\bclassname="([^"]*)"/;
995
1027
  var FAILURE_CHILD_RE = /<failure\b|<error\b/;
1028
+ var SKIPPED_CHILD_RE = /<skipped\b/;
996
1029
  var TESTSUITE_OPEN_RE = /<testsuite\b([^>]*?)>/g;
997
1030
  var FILE_ATTR_RE = /\bfile="([^"]*)"/;
998
1031
  var QP_TOKEN_BRACKET_RE = /\[(QP-[A-Z][A-Z0-9]*-\d+-\d+)\]/;
@@ -1014,8 +1047,10 @@ function extractFailureText(testcaseBody) {
1014
1047
  const text = unescapeXmlEntities(raw);
1015
1048
  return text.length > MAX_FAILURE_TEXT ? `${text.slice(0, MAX_FAILURE_TEXT)}\u2026` : text;
1016
1049
  }
1050
+ var RESULT_RANK = { pass: 0, skipped: 1, fail: 2 };
1017
1051
  function parseJUnitOutcomes(xml) {
1018
1052
  const outcomes = /* @__PURE__ */ new Map();
1053
+ const automationIdResults = /* @__PURE__ */ new Map();
1019
1054
  const failureText = /* @__PURE__ */ new Map();
1020
1055
  const discoveredFiles = /* @__PURE__ */ new Set();
1021
1056
  const discoveredAutomationIds = /* @__PURE__ */ new Set();
@@ -1043,9 +1078,14 @@ function parseJUnitOutcomes(xml) {
1043
1078
  if (file) discoveredFiles.add(normalizeSlashes(file));
1044
1079
  if (classname) discoveredFiles.add(normalizeSlashes(classname));
1045
1080
  const bracket = QP_TOKEN_BRACKET_RE.exec(name)?.[1];
1046
- if (bracket) discoveredAutomationIds.add(bracket);
1081
+ if (bracket) {
1082
+ discoveredAutomationIds.add(bracket);
1083
+ const result = failed ? "fail" : SKIPPED_CHILD_RE.test(body) ? "skipped" : "pass";
1084
+ const seen = automationIdResults.get(bracket);
1085
+ if (!seen || RESULT_RANK[result] > RESULT_RANK[seen]) automationIdResults.set(bracket, result);
1086
+ }
1047
1087
  }
1048
- return { outcomes, failureText, discoveredFiles, discoveredAutomationIds };
1088
+ return { outcomes, automationIdResults, failureText, discoveredFiles, discoveredAutomationIds };
1049
1089
  }
1050
1090
  function readJUnitOutcomes(cwd) {
1051
1091
  const report = findJUnitReport(cwd);
@@ -1056,6 +1096,9 @@ function readJUnitOutcomes(cwd) {
1056
1096
  return null;
1057
1097
  }
1058
1098
  }
1099
+ function readAutomationIdResults(cwd = process.cwd()) {
1100
+ return readJUnitOutcomes(cwd)?.automationIdResults ?? null;
1101
+ }
1059
1102
  function captureTestSnapshot(command, cwd = process.cwd()) {
1060
1103
  const outcome = runTestCommand(command);
1061
1104
  const parsed = readJUnitOutcomes(cwd);
@@ -1126,8 +1169,186 @@ function describeRegressions(check) {
1126
1169
  return parts.join("; ");
1127
1170
  }
1128
1171
 
1172
+ // src/baselineCheck.ts
1173
+ var BASELINE_CHECK_TIMEOUT_MS = 12e4;
1174
+ function shellQuote(value) {
1175
+ return `'${value.split("'").join("'\\''")}'`;
1176
+ }
1177
+ function buildSingleFileCommand(command, path) {
1178
+ const quoted = shellQuote(path);
1179
+ return command.includes("{file}") ? command.split("{file}").join(quoted) : `${command} ${quoted}`;
1180
+ }
1181
+ var isTestTarget2 = (t) => (t.automationIds?.length ?? 0) > 0;
1182
+ function checkableIds(ids, types) {
1183
+ if (!types) return [];
1184
+ return ids.filter((id) => {
1185
+ if (!Object.prototype.hasOwnProperty.call(types, id)) return false;
1186
+ const type = types[id];
1187
+ return typeof type === "string" && type.trim() !== "" && type.trim().toUpperCase() !== "REGRESSION";
1188
+ });
1189
+ }
1190
+ function insideCheckout(cwd, path) {
1191
+ const abs = (0, import_node_path4.resolve)(cwd, path);
1192
+ const rel = (0, import_node_path4.relative)(cwd, abs);
1193
+ return rel === "" || rel.startsWith("..") || (0, import_node_path4.isAbsolute)(rel) ? null : abs;
1194
+ }
1195
+ function writeForCheck(abs, content) {
1196
+ const original = (0, import_node_fs6.existsSync)(abs) ? (0, import_node_fs6.readFileSync)(abs) : null;
1197
+ const createdDirs = [];
1198
+ if (original === null) {
1199
+ const missing = [];
1200
+ for (let d = (0, import_node_path4.dirname)(abs); !(0, import_node_fs6.existsSync)(d); d = (0, import_node_path4.dirname)(d)) missing.push(d);
1201
+ for (const d of missing.reverse()) {
1202
+ (0, import_node_fs6.mkdirSync)(d);
1203
+ createdDirs.push(d);
1204
+ }
1205
+ }
1206
+ (0, import_node_fs6.writeFileSync)(abs, content);
1207
+ return { abs, original, createdDirs };
1208
+ }
1209
+ function restore(written) {
1210
+ for (const file of [...written].reverse()) {
1211
+ try {
1212
+ if (file.original !== null) (0, import_node_fs6.writeFileSync)(file.abs, file.original);
1213
+ else (0, import_node_fs6.rmSync)(file.abs, { force: true });
1214
+ for (const d of [...file.createdDirs].reverse()) {
1215
+ try {
1216
+ (0, import_node_fs6.rmdirSync)(d);
1217
+ } catch {
1218
+ }
1219
+ }
1220
+ } catch (err) {
1221
+ console.error(`Baseline check could not restore ${file.abs}: ${err instanceof Error ? err.message : String(err)}`);
1222
+ }
1223
+ }
1224
+ }
1225
+ function untrackedFiles(cwd) {
1226
+ try {
1227
+ const out = (0, import_node_child_process3.execFileSync)("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
1228
+ cwd,
1229
+ encoding: "utf8",
1230
+ stdio: ["ignore", "pipe", "ignore"],
1231
+ maxBuffer: 64 * 1024 * 1024
1232
+ });
1233
+ return new Set(out.split("\0").filter(Boolean));
1234
+ } catch {
1235
+ return null;
1236
+ }
1237
+ }
1238
+ var mtimeOf = (path) => {
1239
+ try {
1240
+ return (0, import_node_fs6.statSync)(path).mtimeMs;
1241
+ } catch {
1242
+ return null;
1243
+ }
1244
+ };
1245
+ function decideCause(targets, results, cwd) {
1246
+ const codeTargets = targets.filter((t) => !isTestTarget2(t));
1247
+ if (codeTargets.length === 0) return "ALREADY_TRUE";
1248
+ for (const t of codeTargets) {
1249
+ if (t.kind === "NEW_FILE") return "ALREADY_TRUE";
1250
+ const next = results.get(t.path);
1251
+ const abs = insideCheckout(cwd, t.path);
1252
+ if (next === void 0 || abs === null) return "ALREADY_TRUE";
1253
+ try {
1254
+ if ((0, import_node_fs6.readFileSync)(abs, "utf8") !== next) return "ALREADY_TRUE";
1255
+ } catch {
1256
+ return "ALREADY_TRUE";
1257
+ }
1258
+ }
1259
+ return "NO_OP";
1260
+ }
1261
+ async function runBaselineCheck(params) {
1262
+ const { targets, results, testFileCmd, requiredTestTypes } = params;
1263
+ const cwd = params.cwd ?? process.cwd();
1264
+ const runFile = params.runFile ?? runCommandWithTimeout;
1265
+ const nothing = { findings: null, warnings: [] };
1266
+ if (!testFileCmd || testFileCmd.trim() === "") return nothing;
1267
+ const testTargets = targets.filter(isTestTarget2);
1268
+ const toCheck = testTargets.map((target) => ({ target, ids: checkableIds(target.automationIds ?? [], requiredTestTypes) })).filter((entry) => entry.ids.length > 0);
1269
+ if (toCheck.length === 0) {
1270
+ console.log("Baseline check: no case with a known, non-Regression label \u2014 nothing to check.");
1271
+ return nothing;
1272
+ }
1273
+ const warnings = [];
1274
+ const flagged = [];
1275
+ const written = [];
1276
+ const unsafe = /* @__PURE__ */ new Set();
1277
+ const skip = (path, why) => {
1278
+ const message = `Baseline check skipped for ${path}: ${why}`;
1279
+ console.warn(`::warning::${message}`);
1280
+ warnings.push(message);
1281
+ };
1282
+ try {
1283
+ const contentByPath = new Map(results.map((r) => [r.targetPath, r.newContent]));
1284
+ const cause = decideCause(targets, contentByPath, cwd);
1285
+ for (const target of testTargets) {
1286
+ const content = contentByPath.get(target.path);
1287
+ if (content === void 0) continue;
1288
+ const abs = insideCheckout(cwd, target.path);
1289
+ if (abs === null) {
1290
+ unsafe.add(target.path);
1291
+ continue;
1292
+ }
1293
+ written.push(writeForCheck(abs, content));
1294
+ }
1295
+ for (const { target, ids } of toCheck) {
1296
+ if (unsafe.has(target.path)) {
1297
+ skip(target.path, "the path is outside the checkout, so the file was not written or run.");
1298
+ continue;
1299
+ }
1300
+ if (!contentByPath.has(target.path)) continue;
1301
+ const reportBefore = findJUnitReport(cwd);
1302
+ const mtimeBefore = reportBefore ? mtimeOf(reportBefore) : null;
1303
+ const untrackedBefore = untrackedFiles(cwd);
1304
+ const outcome = await runFile(buildSingleFileCommand(testFileCmd, target.path), BASELINE_CHECK_TIMEOUT_MS);
1305
+ if (outcome === "timeout") {
1306
+ skip(
1307
+ target.path,
1308
+ `the single-file command did not finish within ${BASELINE_CHECK_TIMEOUT_MS / 1e3} seconds and was stopped. No test case from this file was flagged.`
1309
+ );
1310
+ } else if (outcome === "error") {
1311
+ skip(target.path, "the single-file command could not be started. No test case from this file was flagged.");
1312
+ } else {
1313
+ const reportAfter = findJUnitReport(cwd);
1314
+ const fresh = reportAfter !== null && (reportAfter !== reportBefore || mtimeOf(reportAfter) !== mtimeBefore);
1315
+ if (!fresh) {
1316
+ skip(
1317
+ target.path,
1318
+ "the single-file command wrote no new JUnit report (it must write the same JUnit report as the test command), so nothing could be checked."
1319
+ );
1320
+ } else {
1321
+ const byId = readAutomationIdResults(cwd);
1322
+ for (const id of ids) if (byId?.get(id) === "pass") flagged.push(id);
1323
+ }
1324
+ }
1325
+ const untrackedAfter = untrackedFiles(cwd);
1326
+ if (untrackedBefore && untrackedAfter) {
1327
+ for (const file of untrackedAfter) {
1328
+ if (untrackedBefore.has(file)) continue;
1329
+ const abs = insideCheckout(cwd, file);
1330
+ if (abs) (0, import_node_fs6.rmSync)(abs, { force: true });
1331
+ }
1332
+ }
1333
+ }
1334
+ if (flagged.length === 0) return { findings: null, warnings };
1335
+ const automationIds = [...new Set(flagged)].slice(0, 200);
1336
+ const list = automationIds.join(", ");
1337
+ warnings.push(
1338
+ cause === "NO_OP" ? `Baseline check: ${automationIds.length} test case(s) passed against the unmodified code, and every file the generated change edited came out unchanged, so the change itself may have been a no-op: ${list}.` : `Baseline check: ${automationIds.length} test case(s) passed against the unmodified code, so they may assert something already true: ${list}. This check cannot tell that from a change that did nothing.`
1339
+ );
1340
+ return { findings: { cause, automationIds }, warnings };
1341
+ } catch (err) {
1342
+ const message = `Baseline check could not run: ${err instanceof Error ? err.message : String(err)}`;
1343
+ console.warn(`::warning::${message}`);
1344
+ return { findings: null, warnings: [...warnings, message] };
1345
+ } finally {
1346
+ restore(written);
1347
+ }
1348
+ }
1349
+
1129
1350
  // src/scopeCheck.ts
1130
- var import_node_fs6 = require("node:fs");
1351
+ var import_node_fs7 = require("node:fs");
1131
1352
  var ROUTE_METHOD_RE = /\b(GET|POST|PUT|PATCH|DELETE)\b/i;
1132
1353
  var ROUTE_PATH_RE = /\/[A-Za-z0-9_\-{}[\]]+(?:\/[A-Za-z0-9_\-:{}[\]]+)+/;
1133
1354
  var ROUTE_EXPORT_RE = /\bexport\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\s*\(/;
@@ -1184,7 +1405,7 @@ function checkApiScope(results, targets, apiChanges) {
1184
1405
  } else {
1185
1406
  let currentContent;
1186
1407
  try {
1187
- currentContent = (0, import_node_fs6.readFileSync)(targetPath, "utf8");
1408
+ currentContent = (0, import_node_fs7.readFileSync)(targetPath, "utf8");
1188
1409
  } catch {
1189
1410
  currentContent = "";
1190
1411
  }
@@ -1203,7 +1424,7 @@ function describeScopeViolations(check) {
1203
1424
  }
1204
1425
 
1205
1426
  // src/targeting.ts
1206
- var import_node_path4 = require("node:path");
1427
+ var import_node_path5 = require("node:path");
1207
1428
  var CONTRACT_VERSION2 = "test-targets-1";
1208
1429
  function listWorkspaceFiles(workspaceRoot) {
1209
1430
  return listFiles(workspaceRoot ?? ".");
@@ -1232,9 +1453,9 @@ function validateTarget(target, workspaceRoot, workspaceListing, deniedPaths) {
1232
1453
  }
1233
1454
  return;
1234
1455
  }
1235
- const root = (0, import_node_path4.resolve)(workspaceRoot ?? ".");
1236
- const rel = (0, import_node_path4.relative)(root, (0, import_node_path4.resolve)(target.path));
1237
- if (rel.startsWith("..") || (0, import_node_path4.isAbsolute)(rel)) {
1456
+ const root = (0, import_node_path5.resolve)(workspaceRoot ?? ".");
1457
+ const rel = (0, import_node_path5.relative)(root, (0, import_node_path5.resolve)(target.path));
1458
+ if (rel.startsWith("..") || (0, import_node_path5.isAbsolute)(rel)) {
1238
1459
  throw new Error(
1239
1460
  `pilot.targeting named a NEW_FILE path that escapes the workspace root: ${target.path}`
1240
1461
  );
@@ -1455,10 +1676,11 @@ async function run(env2) {
1455
1676
  let packet;
1456
1677
  let branch;
1457
1678
  let runToken;
1458
- let warnings = [];
1679
+ const warnings = [];
1459
1680
  let targeted = false;
1460
1681
  let generatedFilePaths = [];
1461
1682
  let targetingResult;
1683
+ let baselineFindings;
1462
1684
  const contextLog = [];
1463
1685
  try {
1464
1686
  runToken = await exchangeForRunToken(env2.tokenUrl, await fetchGithubOidcToken());
@@ -1493,8 +1715,9 @@ async function run(env2) {
1493
1715
  const blockingOverlaps = overlaps.filter((o) => o.severity === "identical");
1494
1716
  const softOverlaps = overlaps.filter((o) => o.severity === "similar");
1495
1717
  if (softOverlaps.length > 0) {
1496
- warnings = describeOverlapWarnings(softOverlaps);
1497
- for (const w of warnings) {
1718
+ const overlapWarnings = describeOverlapWarnings(softOverlaps);
1719
+ warnings.push(...overlapWarnings);
1720
+ for (const w of overlapWarnings) {
1498
1721
  console.warn(`QuikPilot targeting warning (not blocking): ${w}`);
1499
1722
  }
1500
1723
  }
@@ -1544,6 +1767,20 @@ async function run(env2) {
1544
1767
  `QuikPilot's generated tests are not where the test runner will find them \u2014 ${describeTestPlacementViolations(placementCheck)}`
1545
1768
  );
1546
1769
  }
1770
+ try {
1771
+ const baselineCheck = await runBaselineCheck({
1772
+ targets: targeting.targets,
1773
+ results,
1774
+ testFileCmd: packet.commands.testFile,
1775
+ requiredTestTypes: packet.requiredTestTypes
1776
+ });
1777
+ warnings.push(...baselineCheck.warnings);
1778
+ baselineFindings = baselineCheck.findings ?? void 0;
1779
+ } catch (err) {
1780
+ const message = `Baseline check could not run: ${err instanceof Error ? err.message : String(err)}`;
1781
+ console.error(message);
1782
+ warnings.push(message);
1783
+ }
1547
1784
  generatedFilePaths = results.map((r) => r.targetPath);
1548
1785
  flushCodegenResults(results);
1549
1786
  try {
@@ -1615,7 +1852,8 @@ async function run(env2) {
1615
1852
  branch,
1616
1853
  warnings,
1617
1854
  // Omitted when targeting itself never produced a result (nothing exists to record).
1618
- targeting: targetingResult ? buildTargetingRecord(targetingResult, contextLog) : void 0
1855
+ targeting: targetingResult ? buildTargetingRecord(targetingResult, contextLog) : void 0,
1856
+ baselineFindings
1619
1857
  });
1620
1858
  return { hardFailure };
1621
1859
  }