headlamp 0.1.6 → 0.1.9

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/dist/cli.cjs CHANGED
@@ -93,7 +93,7 @@ var init_TimeoutError = __esm({
93
93
 
94
94
  // node_modules/es-toolkit/dist/promise/delay.mjs
95
95
  function delay(ms, { signal } = {}) {
96
- return new Promise((resolve8, reject) => {
96
+ return new Promise((resolve9, reject) => {
97
97
  const abortError = () => {
98
98
  reject(new AbortError());
99
99
  };
@@ -106,7 +106,7 @@ function delay(ms, { signal } = {}) {
106
106
  }
107
107
  const timeoutId = setTimeout(() => {
108
108
  signal?.removeEventListener("abort", abortHandler);
109
- resolve8();
109
+ resolve9();
110
110
  }, ms);
111
111
  signal?.addEventListener("abort", abortHandler, { once: true });
112
112
  });
@@ -171,11 +171,11 @@ var init_exec = __esm({
171
171
  child.stderr?.on("data", (chunk) => {
172
172
  stderr += String(chunk);
173
173
  });
174
- const exec = new Promise((resolve8, reject) => {
174
+ const exec = new Promise((resolve9, reject) => {
175
175
  child.on("error", reject);
176
176
  child.on(
177
177
  "close",
178
- (code) => Number(code) === 0 ? resolve8(stdout) : reject(new Error(stderr || `exit ${code}`))
178
+ (code) => Number(code) === 0 ? resolve9(stdout) : reject(new Error(stderr || `exit ${code}`))
179
179
  );
180
180
  });
181
181
  try {
@@ -195,7 +195,7 @@ var init_exec = __esm({
195
195
  throw caughtError;
196
196
  }
197
197
  };
198
- runExitCode = async (cmd, args, opts = {}) => new Promise((resolve8, reject) => {
198
+ runExitCode = async (cmd, args, opts = {}) => new Promise((resolve9, reject) => {
199
199
  const child = (0, import_node_child_process.spawn)(cmd, [...args], {
200
200
  cwd: opts.cwd,
201
201
  env: opts.env,
@@ -204,9 +204,9 @@ var init_exec = __esm({
204
204
  windowsHide: true
205
205
  });
206
206
  child.on("error", reject);
207
- child.on("close", (code) => resolve8(Number(code)));
207
+ child.on("close", (code) => resolve9(Number(code)));
208
208
  });
209
- runWithCapture = async (cmd, args, opts) => new Promise((resolve8, reject) => {
209
+ runWithCapture = async (cmd, args, opts) => new Promise((resolve9, reject) => {
210
210
  const child = (0, import_node_child_process.spawn)(cmd, [...args], {
211
211
  cwd: opts.cwd,
212
212
  env: opts.env,
@@ -222,7 +222,7 @@ var init_exec = __esm({
222
222
  buf += String(chunk);
223
223
  });
224
224
  child.on("error", reject);
225
- child.on("close", (code) => resolve8({ code: Number(code), output: buf }));
225
+ child.on("close", (code) => resolve9({ code: Number(code), output: buf }));
226
226
  });
227
227
  }
228
228
  });
@@ -248,6 +248,7 @@ var init_args = __esm({
248
248
  coverage: (coverageValue) => ({ type: "coverage", coverageValue }),
249
249
  coverageUi: (value) => ({ type: "coverageUi", value }),
250
250
  coverageAbortOnFailure: (value) => ({ type: "coverageAbortOnFailure", value }),
251
+ onlyFailures: (value) => ({ type: "onlyFailures", value }),
251
252
  jestArg: (value) => ({ type: "jestArg", value }),
252
253
  jestArgs: (values) => ({ type: "jestArgs", values }),
253
254
  vitestArg: (value) => ({ type: "vitestArg", value }),
@@ -263,7 +264,8 @@ var init_args = __esm({
263
264
  coverageMode: (value) => ({ type: "coverageMode", value }),
264
265
  coverageMaxFiles: (value) => ({ type: "coverageMaxFiles", value }),
265
266
  coverageMaxHotspots: (value) => ({ type: "coverageMaxHotspots", value }),
266
- coveragePageFit: (value) => ({ type: "coveragePageFit", value })
267
+ coveragePageFit: (value) => ({ type: "coveragePageFit", value }),
268
+ changed: (value) => ({ type: "changed", value })
267
269
  };
268
270
  Some = (value) => ({ _tag: "some", value });
269
271
  None = { _tag: "none" };
@@ -400,6 +402,18 @@ var init_args = __esm({
400
402
  "--coverage.pageFit",
401
403
  (_flag, lookahead) => step([ActionBuilders.coveragePageFit(isTruthy(String(lookahead)))], true)
402
404
  ),
405
+ // --onlyFailures flag (boolean)
406
+ rule.eq("--onlyFailures", () => step([ActionBuilders.onlyFailures(true)])),
407
+ rule.startsWith(
408
+ "--onlyFailures=",
409
+ (value) => step([
410
+ ActionBuilders.onlyFailures(isTruthy((value.split("=")[1] ?? "").trim().toLowerCase()))
411
+ ])
412
+ ),
413
+ rule.withLookahead(
414
+ "--onlyFailures",
415
+ (_flag, lookahead) => step([ActionBuilders.onlyFailures(isTruthy(String(lookahead)))], true)
416
+ ),
403
417
  rule.withLookahead(
404
418
  "--testPathPattern",
405
419
  (flag, lookahead) => step([ActionBuilders.jestArgs([flag, lookahead])], true)
@@ -460,6 +474,18 @@ var init_args = __esm({
460
474
  "--coverage.root=",
461
475
  (value) => step([ActionBuilders.coverageRoot((value.split("=")[1] ?? "").trim())])
462
476
  ),
477
+ // --changed flag: selects changed files via git (all|staged|unstaged)
478
+ rule.eq("--changed", () => step([ActionBuilders.changed("all")])),
479
+ rule.startsWith("--changed=", (value) => {
480
+ const raw = (value.split("=")[1] ?? "").trim().toLowerCase();
481
+ const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : "all";
482
+ return step([ActionBuilders.changed(mode)]);
483
+ }),
484
+ rule.withLookahead("--changed", (_flag, lookahead) => {
485
+ const raw = String(lookahead).trim().toLowerCase();
486
+ const mode = raw === "staged" ? "staged" : raw === "unstaged" ? "unstaged" : "all";
487
+ return step([ActionBuilders.changed(mode)], true);
488
+ }),
463
489
  rule.withLookahead(
464
490
  "-t",
465
491
  (flag, lookahead) => step(
@@ -534,6 +560,8 @@ var init_args = __esm({
534
560
  return { vitest: [], jest: [], coverage: false, coverageUi: action.value };
535
561
  case "coverageAbortOnFailure":
536
562
  return { vitest: [], jest: [], coverage: false, coverageAbortOnFailure: action.value };
563
+ case "onlyFailures":
564
+ return { vitest: [], jest: [], coverage: false, onlyFailures: action.value };
537
565
  case "jestArgs":
538
566
  return { vitest: [], jest: action.values, coverage: false };
539
567
  case "selectionHint":
@@ -562,6 +590,8 @@ var init_args = __esm({
562
590
  return { vitest: [], jest: [], coverage: false, coverageMaxHotspots: action.value };
563
591
  case "coveragePageFit":
564
592
  return { vitest: [], jest: [], coverage: false, coveragePageFit: action.value };
593
+ case "changed":
594
+ return { vitest: [], jest: [], coverage: false, changed: action.value };
565
595
  case "jestArg":
566
596
  return { vitest: [], jest: [action.value], coverage: false };
567
597
  case "vitestArg":
@@ -601,7 +631,9 @@ var init_args = __esm({
601
631
  }
602
632
  return {
603
633
  ...next,
634
+ ...right.changed !== void 0 || left.changed !== void 0 ? { changed: right.changed ?? left.changed } : {},
604
635
  ...right.coverageAbortOnFailure !== void 0 || left.coverageAbortOnFailure !== void 0 ? { coverageAbortOnFailure: right.coverageAbortOnFailure ?? left.coverageAbortOnFailure } : {},
636
+ ...right.onlyFailures !== void 0 || left.onlyFailures !== void 0 ? { onlyFailures: right.onlyFailures ?? left.onlyFailures } : {},
605
637
  ...right.coverageDetail !== void 0 || left.coverageDetail !== void 0 ? { coverageDetail: right.coverageDetail ?? left.coverageDetail } : {},
606
638
  ...right.coverageShowCode !== void 0 || left.coverageShowCode !== void 0 ? { coverageShowCode: right.coverageShowCode ?? left.coverageShowCode } : {},
607
639
  ...right.coverageMode !== void 0 || left.coverageMode !== void 0 ? { coverageMode: right.coverageMode ?? left.coverageMode } : {},
@@ -625,6 +657,7 @@ var init_args = __esm({
625
657
  let collectCoverage = false;
626
658
  let coverageUi = "both";
627
659
  let coverageAbortOnFailure = false;
660
+ let onlyFailures = false;
628
661
  let coverageShowCode = Boolean(process.stdout.isTTY);
629
662
  let coverageMode = "auto";
630
663
  const coverageMaxFilesLocalInit = void 0;
@@ -642,6 +675,7 @@ var init_args = __esm({
642
675
  collectCoverage ||= contrib.coverage;
643
676
  coverageUi = contrib.coverageUi ?? coverageUi;
644
677
  coverageAbortOnFailure = contrib.coverageAbortOnFailure ?? coverageAbortOnFailure;
678
+ onlyFailures = contrib.onlyFailures ?? onlyFailures;
645
679
  coverageShowCode = contrib.coverageShowCode ?? coverageShowCode;
646
680
  const coverageDetailComputed = contrib.coverageDetail ?? (contrib.selection ? "auto" : void 0);
647
681
  coverageMode = contrib.coverageMode ?? (contrib.selection ? "compact" : "auto");
@@ -675,6 +709,7 @@ var init_args = __esm({
675
709
  collectCoverage,
676
710
  coverageUi,
677
711
  coverageAbortOnFailure,
712
+ onlyFailures,
678
713
  selectionSpecified: Boolean(contrib.selection),
679
714
  selectionPaths: [...contrib.selectionPaths ?? []],
680
715
  includeGlobs,
@@ -686,7 +721,8 @@ var init_args = __esm({
686
721
  ...coverageMaxHotspotsLocal !== void 0 ? { coverageMaxHotspots: coverageMaxHotspotsLocal } : {},
687
722
  coveragePageFit,
688
723
  ...contrib.editorCmd !== void 0 ? { editorCmd: contrib.editorCmd } : {},
689
- ...contrib.workspaceRoot !== void 0 ? { workspaceRoot: contrib.workspaceRoot } : {}
724
+ ...contrib.workspaceRoot !== void 0 ? { workspaceRoot: contrib.workspaceRoot } : {},
725
+ ...contrib.changed !== void 0 ? { changed: contrib.changed } : {}
690
726
  };
691
727
  return out;
692
728
  };
@@ -1960,7 +1996,7 @@ var require_lib = __commonJS({
1960
1996
  });
1961
1997
 
1962
1998
  // src/lib/program.ts
1963
- var path9 = __toESM(require("node:path"), 1);
1999
+ var path10 = __toESM(require("node:path"), 1);
1964
2000
  var os2 = __toESM(require("node:os"), 1);
1965
2001
  var fsSync3 = __toESM(require("node:fs"), 1);
1966
2002
  var fs5 = __toESM(require("node:fs/promises"), 1);
@@ -2921,7 +2957,7 @@ var compositeBarPct = (summary, hotspots) => {
2921
2957
  };
2922
2958
 
2923
2959
  // src/lib/coverage-print.ts
2924
- var path7 = __toESM(require("node:path"), 1);
2960
+ var path8 = __toESM(require("node:path"), 1);
2925
2961
  var fsSync2 = __toESM(require("node:fs"), 1);
2926
2962
  init_env_utils();
2927
2963
  init_exec();
@@ -3091,6 +3127,71 @@ var renderTable = (columns, rows) => {
3091
3127
  };
3092
3128
  var barCell = (pct) => (padded) => bar(pct, padded.length);
3093
3129
 
3130
+ // src/lib/relevance.ts
3131
+ var path7 = __toESM(require("node:path"), 1);
3132
+ init_args();
3133
+ init_fast_related();
3134
+ var normalizeAbs = (inputPath) => path7.resolve(inputPath).replace(/\\/g, "/");
3135
+ var compareBooleanDesc = (left, right) => {
3136
+ if (left === right) {
3137
+ return 0;
3138
+ }
3139
+ return right ? 1 : -1;
3140
+ };
3141
+ var compareNumberAsc = (left, right) => left - right;
3142
+ var compareStringAsc = (left, right) => left.localeCompare(right);
3143
+ var fileFailed = (file) => Boolean(
3144
+ (file.status ?? "") === "failed" || (file.testResults ?? []).some((assertion) => (assertion.status ?? "") === "failed")
3145
+ );
3146
+ var composeComparators = (...comparators) => (left, right) => {
3147
+ for (const cmp of comparators) {
3148
+ const result = cmp(left, right);
3149
+ if (result !== 0) {
3150
+ return result;
3151
+ }
3152
+ }
3153
+ return 0;
3154
+ };
3155
+ var comparatorForRank = (rankByPath) => {
3156
+ const rankOrInf = (absPath) => rankByPath.has(absPath) ? rankByPath.get(absPath) : Number.POSITIVE_INFINITY;
3157
+ return composeComparators(
3158
+ (left, right) => compareBooleanDesc(fileFailed(left), fileFailed(right)),
3159
+ (left, right) => compareNumberAsc(
3160
+ rankOrInf(normalizeAbs(left.testFilePath)),
3161
+ rankOrInf(normalizeAbs(right.testFilePath))
3162
+ ),
3163
+ (left, right) => compareStringAsc(normalizeAbs(left.testFilePath), normalizeAbs(right.testFilePath))
3164
+ );
3165
+ };
3166
+ var computeDirectnessRank = async (opts) => {
3167
+ const selectionKey = opts.productionSeeds.map((abs) => path7.relative(opts.repoRoot, abs).replace(/\\/g, "/")).sort((left, right) => left.localeCompare(right)).join("|");
3168
+ const related = await cachedRelated({
3169
+ repoRoot: opts.repoRoot,
3170
+ selectionKey,
3171
+ compute: () => findRelatedTestsFast({
3172
+ repoRoot: opts.repoRoot,
3173
+ productionPaths: opts.productionSeeds,
3174
+ testGlobs: DEFAULT_TEST_GLOBS,
3175
+ excludeGlobs: opts.excludeGlobs ?? DEFAULT_EXCLUDE,
3176
+ timeoutMs: 1500
3177
+ })
3178
+ });
3179
+ const out = /* @__PURE__ */ new Map();
3180
+ related.forEach((abs, index) => {
3181
+ out.set(normalizeAbs(abs), index);
3182
+ });
3183
+ return out;
3184
+ };
3185
+ var sortTestResultsWithRank = (rankByPath, results) => results.slice().sort(comparatorForRank(rankByPath));
3186
+ var comparatorForPathRank = (rankByPath) => {
3187
+ const rankOrInf = (absPath) => rankByPath.has(absPath) ? rankByPath.get(absPath) : Number.POSITIVE_INFINITY;
3188
+ return composeComparators(
3189
+ (left, right) => compareNumberAsc(rankOrInf(normalizeAbs(left)), rankOrInf(normalizeAbs(right))),
3190
+ (left, right) => compareStringAsc(normalizeAbs(left), normalizeAbs(right))
3191
+ );
3192
+ };
3193
+ var sortPathsWithRank = (rankByPath, paths) => paths.slice().sort(comparatorForPathRank(rankByPath));
3194
+
3094
3195
  // src/lib/coverage-print.ts
3095
3196
  var printDetailedCoverage = async (opts) => {
3096
3197
  const files = opts.map.files().sort((fileA, fileB) => {
@@ -3101,7 +3202,7 @@ var printDetailedCoverage = async (opts) => {
3101
3202
  for (const abs of files) {
3102
3203
  const fc = opts.map.fileCoverageFor(abs);
3103
3204
  const sum = fc.toSummary();
3104
- const rel = path7.relative(opts.root, abs).replace(/\\/g, "/");
3205
+ const rel = path8.relative(opts.root, abs).replace(/\\/g, "/");
3105
3206
  const blocks = computeUncoveredBlocks(fc);
3106
3207
  const misses = missedBranches(fc);
3107
3208
  const missFns = missedFunctions(fc);
@@ -3110,9 +3211,9 @@ var printDetailedCoverage = async (opts) => {
3110
3211
  const branchesPctText = `${sum.branches.pct.toFixed(1)}%`;
3111
3212
  const header = `${ansi.bold(rel)} lines ${tintPct(sum.lines.pct)(
3112
3213
  linesPctText
3113
- )} ${barCell(compositeBarPct(sum, blocks))("".padEnd(14))} funcs ${tintPct(
3114
- sum.functions.pct
3115
- )(funcsPctText)} branches ${tintPct(sum.branches.pct)(branchesPctText)}`;
3214
+ )} ${barCell(compositeBarPct(sum, blocks))("".padEnd(14))} funcs ${tintPct(sum.functions.pct)(
3215
+ funcsPctText
3216
+ )} branches ${tintPct(sum.branches.pct)(branchesPctText)}`;
3116
3217
  console.info(header);
3117
3218
  const max = opts.limitPerFile === "all" ? Number.POSITIVE_INFINITY : opts.limitPerFile ?? 5;
3118
3219
  const compareRangesByLengthDescThenStart = (firstRange, secondRange) => {
@@ -3139,10 +3240,8 @@ var printDetailedCoverage = async (opts) => {
3139
3240
  abs,
3140
3241
  block.start,
3141
3242
  opts.editorCmd
3142
- )}\x07${path7.basename(abs)}:${block.start}\x1B]8;;\x07`;
3143
- const label = ` ${ansi.yellow(`L${block.start}`)}\u2013${ansi.yellow(
3144
- `L${block.end}`
3145
- )} ${link}`;
3243
+ )}\x07${path8.basename(abs)}:${block.start}\x1B]8;;\x07`;
3244
+ const label = ` ${ansi.yellow(`L${block.start}`)}\u2013${ansi.yellow(`L${block.end}`)} ${link}`;
3146
3245
  console.info(label);
3147
3246
  if (opts.showCode && src.length) {
3148
3247
  const lines = src.split(/\r?\n/);
@@ -3162,7 +3261,7 @@ var printDetailedCoverage = async (opts) => {
3162
3261
  abs,
3163
3262
  fn.line,
3164
3263
  opts.editorCmd
3165
- )}\x07${path7.basename(abs)}:${fn.line}\x1B]8;;\x07`;
3264
+ )}\x07${path8.basename(abs)}:${fn.line}\x1B]8;;\x07`;
3166
3265
  console.info(` - ${fn.name} @ ${link}`);
3167
3266
  }
3168
3267
  }
@@ -3173,12 +3272,8 @@ var printDetailedCoverage = async (opts) => {
3173
3272
  abs,
3174
3273
  br.line,
3175
3274
  opts.editorCmd
3176
- )}\x07${path7.basename(abs)}:${br.line}\x1B]8;;\x07`;
3177
- console.info(
3178
- ` - branch#${br.id} @ ${link} missed paths: [${br.zeroPaths.join(
3179
- ", "
3180
- )}]`
3181
- );
3275
+ )}\x07${path8.basename(abs)}:${br.line}\x1B]8;;\x07`;
3276
+ console.info(` - branch#${br.id} @ ${link} missed paths: [${br.zeroPaths.join(", ")}]`);
3182
3277
  }
3183
3278
  }
3184
3279
  console.info("");
@@ -3198,7 +3293,7 @@ var printCompactCoverage = async (opts) => {
3198
3293
  for (const abs of files.slice(0, fileCap)) {
3199
3294
  const fc = opts.map.fileCoverageFor(abs);
3200
3295
  const sum = fc.toSummary();
3201
- const rel = path7.relative(opts.root, abs).replace(/\\/g, "/");
3296
+ const rel = path8.relative(opts.root, abs).replace(/\\/g, "/");
3202
3297
  const compareRangesByLengthDescThenStart = (firstRange, secondRange) => {
3203
3298
  const secondLength = secondRange.end - secondRange.start;
3204
3299
  const firstLength = firstRange.end - firstRange.start;
@@ -3212,9 +3307,9 @@ var printCompactCoverage = async (opts) => {
3212
3307
  const branchesPctText = `${sum.branches.pct.toFixed(1)}%`;
3213
3308
  const header = `${ansi.bold(rel)} lines ${tintPct(sum.lines.pct)(
3214
3309
  linesPctText
3215
- )} ${barCell(compositeBarPct(sum, blocks))("".padEnd(14))} funcs ${tintPct(
3216
- sum.functions.pct
3217
- )(funcsPctText)} branches ${tintPct(sum.branches.pct)(branchesPctText)}`;
3310
+ )} ${barCell(compositeBarPct(sum, blocks))("".padEnd(14))} funcs ${tintPct(sum.functions.pct)(
3311
+ funcsPctText
3312
+ )} branches ${tintPct(sum.branches.pct)(branchesPctText)}`;
3218
3313
  console.info(header);
3219
3314
  const hotspots = blocks.slice(0, maxHotspotsDerived);
3220
3315
  if (hotspots.length) {
@@ -3225,10 +3320,8 @@ var printCompactCoverage = async (opts) => {
3225
3320
  abs,
3226
3321
  hotspot.start,
3227
3322
  opts.editorCmd
3228
- )}\x07${path7.basename(abs)}:${hotspot.start}\x1B]8;;\x07`;
3229
- console.info(
3230
- ` - L${hotspot.start}\u2013L${hotspot.end} (${len} lines) ${link}`
3231
- );
3323
+ )}\x07${path8.basename(abs)}:${hotspot.start}\x1B]8;;\x07`;
3324
+ console.info(` - L${hotspot.start}\u2013L${hotspot.end} (${len} lines) ${link}`);
3232
3325
  }
3233
3326
  }
3234
3327
  const functionsList = missFns.slice(0, maxFunctionsDerived);
@@ -3240,7 +3333,7 @@ var printCompactCoverage = async (opts) => {
3240
3333
  abs,
3241
3334
  fn.line,
3242
3335
  opts.editorCmd
3243
- )}\x07${path7.basename(abs)}:${fn.line}\x1B]8;;\x07`
3336
+ )}\x07${path8.basename(abs)}:${fn.line}\x1B]8;;\x07`
3244
3337
  );
3245
3338
  }
3246
3339
  }
@@ -3255,7 +3348,7 @@ var printCompactCoverage = async (opts) => {
3255
3348
  abs,
3256
3349
  br.line,
3257
3350
  opts.editorCmd
3258
- )}\x07${path7.basename(abs)}:${br.line}\x1B]8;;\x07`
3351
+ )}\x07${path8.basename(abs)}:${br.line}\x1B]8;;\x07`
3259
3352
  );
3260
3353
  }
3261
3354
  }
@@ -3264,9 +3357,7 @@ var printCompactCoverage = async (opts) => {
3264
3357
  const restBrs = Math.max(0, misses.length - branchesList.length);
3265
3358
  if (restHs + restFns + restBrs > 0) {
3266
3359
  console.info(
3267
- ansi.dim(
3268
- ` \u2026 truncated: +${restHs} hotspots, +${restFns} funcs, +${restBrs} branches`
3269
- )
3360
+ ansi.dim(` \u2026 truncated: +${restHs} hotspots, +${restFns} funcs, +${restBrs} branches`)
3270
3361
  );
3271
3362
  }
3272
3363
  console.info("");
@@ -3303,7 +3394,7 @@ var shortenPathPreservingFilename = (relPath, maxWidth, opts) => {
3303
3394
  return { stem: base.slice(0, -ending.length), ext: ending };
3304
3395
  }
3305
3396
  }
3306
- const ext2 = path7.extname(base);
3397
+ const ext2 = path8.extname(base);
3307
3398
  return { stem: base.slice(0, -ext2.length), ext: ext2 };
3308
3399
  };
3309
3400
  const sliceBalanced = (input, width) => {
@@ -3386,12 +3477,7 @@ var shortenPathPreservingFilename = (relPath, maxWidth, opts) => {
3386
3477
  const tailParts = tailSrc.map((segment) => segment);
3387
3478
  let hidAny = false;
3388
3479
  const build = () => {
3389
- const label2 = joinParts(
3390
- headParts,
3391
- tailParts,
3392
- hideMiddle2 || hidAny,
3393
- baseLabel
3394
- );
3480
+ const label2 = joinParts(headParts, tailParts, hideMiddle2 || hidAny, baseLabel);
3395
3481
  return { label: label2, width: visibleWidth(label2) };
3396
3482
  };
3397
3483
  let { label, width } = build();
@@ -3484,13 +3570,7 @@ var shortenPathPreservingFilename = (relPath, maxWidth, opts) => {
3484
3570
  return { headRaw: headRaw2, tailRaw: tailRaw2, hideMiddle: hideMiddle2 };
3485
3571
  };
3486
3572
  let { headRaw, tailRaw, hideMiddle } = buildRaw(headCount, tailCount);
3487
- let candidate = tryTrimDirsToFit(
3488
- headRaw,
3489
- tailRaw,
3490
- hideMiddle,
3491
- baseFull,
3492
- maxWidth
3493
- );
3573
+ let candidate = tryTrimDirsToFit(headRaw, tailRaw, hideMiddle, baseFull, maxWidth);
3494
3574
  if (!candidate) {
3495
3575
  return baseFull;
3496
3576
  }
@@ -3499,13 +3579,7 @@ var shortenPathPreservingFilename = (relPath, maxWidth, opts) => {
3499
3579
  if (headCount + tailCount < total) {
3500
3580
  const tryTail = Math.min(tailCount + 1, total - headCount);
3501
3581
  ({ headRaw, tailRaw, hideMiddle } = buildRaw(headCount, tryTail));
3502
- const candTail = tryTrimDirsToFit(
3503
- headRaw,
3504
- tailRaw,
3505
- hideMiddle,
3506
- baseFull,
3507
- maxWidth
3508
- );
3582
+ const candTail = tryTrimDirsToFit(headRaw, tailRaw, hideMiddle, baseFull, maxWidth);
3509
3583
  if (candTail) {
3510
3584
  tailCount = tryTail;
3511
3585
  candidate = candTail;
@@ -3515,13 +3589,7 @@ var shortenPathPreservingFilename = (relPath, maxWidth, opts) => {
3515
3589
  if (!advanced && headCount + tailCount < total) {
3516
3590
  const tryHead = Math.min(headCount + 1, total - tailCount);
3517
3591
  ({ headRaw, tailRaw, hideMiddle } = buildRaw(tryHead, tailCount));
3518
- const candHead = tryTrimDirsToFit(
3519
- headRaw,
3520
- tailRaw,
3521
- hideMiddle,
3522
- baseFull,
3523
- maxWidth
3524
- );
3592
+ const candHead = tryTrimDirsToFit(headRaw, tailRaw, hideMiddle, baseFull, maxWidth);
3525
3593
  if (candHead) {
3526
3594
  headCount = tryHead;
3527
3595
  candidate = candHead;
@@ -3583,7 +3651,7 @@ var buildDistanceMapFromTests = async (executedTestsAbs, rootDir) => {
3583
3651
  const queue = [];
3584
3652
  const seen = /* @__PURE__ */ new Set();
3585
3653
  for (const testAbs of executedTestsAbs) {
3586
- const testPathNormalized = path7.resolve(testAbs).replace(/\\/g, "/");
3654
+ const testPathNormalized = path8.resolve(testAbs).replace(/\\/g, "/");
3587
3655
  dist.set(testPathNormalized, 0);
3588
3656
  queue.push([testPathNormalized, 0]);
3589
3657
  }
@@ -3602,12 +3670,7 @@ var buildDistanceMapFromTests = async (executedTestsAbs, rootDir) => {
3602
3670
  const specs = await extractImportSpecs2(currentFile, specsCache);
3603
3671
  const nextDistance = currentDistance + 1;
3604
3672
  for (const spec of specs) {
3605
- const resolved = resolveImportWithRoot(
3606
- currentFile,
3607
- spec,
3608
- rootDir,
3609
- resolutionCache
3610
- );
3673
+ const resolved = resolveImportWithRoot(currentFile, spec, rootDir, resolutionCache);
3611
3674
  const usable = resolved && !resolved.includes("/node_modules/");
3612
3675
  if (usable) {
3613
3676
  const existing = dist.get(resolved);
@@ -3622,13 +3685,10 @@ var buildDistanceMapFromTests = async (executedTestsAbs, rootDir) => {
3622
3685
  return dist;
3623
3686
  };
3624
3687
  var renderPerFileCompositeTable = async (opts) => {
3625
- const rel = path7.relative(opts.root, opts.absPath).replace(/\\/g, "/");
3688
+ const rel = path8.relative(opts.root, opts.absPath).replace(/\\/g, "/");
3626
3689
  const sum = opts.file.toSummary();
3627
3690
  const rowsAvail = typeof process.stdout.rows === "number" && process.stdout.rows > 10 ? process.stdout.rows : 40;
3628
- const tableBudget = Math.max(
3629
- 14,
3630
- Math.min(opts.maxRows ?? rowsAvail - 1, rowsAvail + 8)
3631
- );
3691
+ const tableBudget = Math.max(14, Math.min(opts.maxRows ?? rowsAvail - 1, rowsAvail + 8));
3632
3692
  const rowBudget = Math.max(6, tableBudget - 6);
3633
3693
  const blocks = computeUncoveredBlocks(opts.file).slice().sort((firstRange, secondRange) => {
3634
3694
  const firstLength = firstRange.end - firstRange.start;
@@ -3672,9 +3732,7 @@ var renderPerFileCompositeTable = async (opts) => {
3672
3732
  rows.push([
3673
3733
  cell(
3674
3734
  rel,
3675
- (padded) => ansi.dim(
3676
- shortenPathPreservingFilename(rel, padded.length).padEnd(padded.length)
3677
- )
3735
+ (padded) => ansi.dim(shortenPathPreservingFilename(rel, padded.length).padEnd(padded.length))
3678
3736
  ),
3679
3737
  cell("Totals", ansi.dim),
3680
3738
  cell("\u2014", ansi.dim),
@@ -3693,11 +3751,7 @@ var renderPerFileCompositeTable = async (opts) => {
3693
3751
  rows.push([
3694
3752
  cell(
3695
3753
  rel,
3696
- (padded) => ansi.dim(
3697
- shortenPathPreservingFilename(rel, padded.length).padEnd(
3698
- padded.length
3699
- )
3700
- )
3754
+ (padded) => ansi.dim(shortenPathPreservingFilename(rel, padded.length).padEnd(padded.length))
3701
3755
  ),
3702
3756
  cell("Hotspots", ansi.dim),
3703
3757
  cell("", ansi.dim),
@@ -3711,14 +3765,8 @@ var renderPerFileCompositeTable = async (opts) => {
3711
3765
  rows.push([
3712
3766
  cell(rel, (padded) => {
3713
3767
  const width = padded.length;
3714
- const display = shortenPathPreservingFilename(rel, width).padEnd(
3715
- width
3716
- );
3717
- return linkifyPadded(
3718
- opts.absPath,
3719
- hotspotRange.start,
3720
- opts.editorCmd
3721
- )(display);
3768
+ const display = shortenPathPreservingFilename(rel, width).padEnd(width);
3769
+ return linkifyPadded(opts.absPath, hotspotRange.start, opts.editorCmd)(display);
3722
3770
  }),
3723
3771
  cell("Hotspot"),
3724
3772
  cell(`L${hotspotRange.start}\u2013L${hotspotRange.end}`),
@@ -3735,11 +3783,7 @@ var renderPerFileCompositeTable = async (opts) => {
3735
3783
  rows.push([
3736
3784
  cell(
3737
3785
  rel,
3738
- (padded) => ansi.dim(
3739
- shortenPathPreservingFilename(rel, padded.length).padEnd(
3740
- padded.length
3741
- )
3742
- )
3786
+ (padded) => ansi.dim(shortenPathPreservingFilename(rel, padded.length).padEnd(padded.length))
3743
3787
  ),
3744
3788
  cell("Functions", ansi.dim),
3745
3789
  cell("", ansi.dim),
@@ -3753,14 +3797,8 @@ var renderPerFileCompositeTable = async (opts) => {
3753
3797
  rows.push([
3754
3798
  cell(rel, (padded) => {
3755
3799
  const width = padded.length;
3756
- const display = shortenPathPreservingFilename(rel, width).padEnd(
3757
- width
3758
- );
3759
- return linkifyPadded(
3760
- opts.absPath,
3761
- missedFunction.line,
3762
- opts.editorCmd
3763
- )(display);
3800
+ const display = shortenPathPreservingFilename(rel, width).padEnd(width);
3801
+ return linkifyPadded(opts.absPath, missedFunction.line, opts.editorCmd)(display);
3764
3802
  }),
3765
3803
  cell("Func"),
3766
3804
  cell(`L${missedFunction.line}`),
@@ -3777,11 +3815,7 @@ var renderPerFileCompositeTable = async (opts) => {
3777
3815
  rows.push([
3778
3816
  cell(
3779
3817
  rel,
3780
- (padded) => ansi.dim(
3781
- shortenPathPreservingFilename(rel, padded.length).padEnd(
3782
- padded.length
3783
- )
3784
- )
3818
+ (padded) => ansi.dim(shortenPathPreservingFilename(rel, padded.length).padEnd(padded.length))
3785
3819
  ),
3786
3820
  cell("Branches", ansi.dim),
3787
3821
  cell("", ansi.dim),
@@ -3795,14 +3829,8 @@ var renderPerFileCompositeTable = async (opts) => {
3795
3829
  rows.push([
3796
3830
  cell(rel, (padded) => {
3797
3831
  const width = padded.length;
3798
- const display = shortenPathPreservingFilename(rel, width).padEnd(
3799
- width
3800
- );
3801
- return linkifyPadded(
3802
- opts.absPath,
3803
- missedBranch.line,
3804
- opts.editorCmd
3805
- )(display);
3832
+ const display = shortenPathPreservingFilename(rel, width).padEnd(width);
3833
+ return linkifyPadded(opts.absPath, missedBranch.line, opts.editorCmd)(display);
3806
3834
  }),
3807
3835
  cell("Branch"),
3808
3836
  cell(`L${missedBranch.line}`),
@@ -3810,9 +3838,7 @@ var renderPerFileCompositeTable = async (opts) => {
3810
3838
  cell(""),
3811
3839
  cell(""),
3812
3840
  cell(""),
3813
- cell(
3814
- `#${missedBranch.id} missed [${missedBranch.zeroPaths.join(", ")}]`
3815
- )
3841
+ cell(`#${missedBranch.id} missed [${missedBranch.zeroPaths.join(", ")}]`)
3816
3842
  ]);
3817
3843
  }
3818
3844
  }
@@ -3832,9 +3858,7 @@ var renderPerFileCompositeTable = async (opts) => {
3832
3858
  rows.push([
3833
3859
  cell(rel, (padded) => {
3834
3860
  const width = padded.length;
3835
- const display = shortenPathPreservingFilename(rel, width).padEnd(
3836
- width
3837
- );
3861
+ const display = shortenPathPreservingFilename(rel, width).padEnd(width);
3838
3862
  return linkifyPadded(opts.absPath, ln, opts.editorCmd)(display);
3839
3863
  }),
3840
3864
  cell("Line"),
@@ -3847,16 +3871,7 @@ var renderPerFileCompositeTable = async (opts) => {
3847
3871
  ]);
3848
3872
  }
3849
3873
  while (rows.length < target) {
3850
- rows.push([
3851
- cell(""),
3852
- cell(""),
3853
- cell(""),
3854
- cell(""),
3855
- cell(""),
3856
- cell(""),
3857
- cell(""),
3858
- cell("")
3859
- ]);
3874
+ rows.push([cell(""), cell(""), cell(""), cell(""), cell(""), cell(""), cell(""), cell("")]);
3860
3875
  }
3861
3876
  }
3862
3877
  }
@@ -3864,20 +3879,17 @@ var renderPerFileCompositeTable = async (opts) => {
3864
3879
  console.info(table);
3865
3880
  const sep = ansi.gray(
3866
3881
  "\u2500".repeat(
3867
- Math.max(
3868
- 20,
3869
- typeof process.stdout.columns === "number" ? process.stdout.columns : 100
3870
- )
3882
+ Math.max(20, typeof process.stdout.columns === "number" ? process.stdout.columns : 100)
3871
3883
  )
3872
3884
  );
3873
3885
  console.info(sep);
3874
3886
  };
3875
3887
  var printPerFileCompositeTables = async (opts) => {
3876
3888
  const selectionAbs = (opts.selectionPaths ?? []).map(
3877
- (selPath) => path7.resolve(selPath).replace(/\\/g, "/")
3889
+ (selPath) => path8.resolve(selPath).replace(/\\/g, "/")
3878
3890
  );
3879
3891
  const changedAbs = (opts.changedFiles ?? []).map(
3880
- (chgPath) => path7.resolve(chgPath).replace(/\\/g, "/")
3892
+ (chgPath) => path8.resolve(chgPath).replace(/\\/g, "/")
3881
3893
  );
3882
3894
  const tokenizeForSimilarity = (filePathForTokens) => new Set(
3883
3895
  filePathForTokens.toLowerCase().replace(/[^a-z0-9/_\-.]/g, " ").split(/[/_.-]+/).filter(Boolean)
@@ -3893,15 +3905,15 @@ var printPerFileCompositeTables = async (opts) => {
3893
3905
  return intersectionCount / unionSize;
3894
3906
  };
3895
3907
  const isSameDirOrChild = (firstAbs, secondAbs) => {
3896
- const dirA = path7.dirname(firstAbs).replace(/\\/g, "/");
3897
- const dirB = path7.dirname(secondAbs).replace(/\\/g, "/");
3908
+ const dirA = path8.dirname(firstAbs).replace(/\\/g, "/");
3909
+ const dirB = path8.dirname(secondAbs).replace(/\\/g, "/");
3898
3910
  return dirA === dirB || dirB.startsWith(`${dirA}/`) || dirA.startsWith(`${dirB}/`);
3899
3911
  };
3900
3912
  const selectionTokens = selectionAbs.map(tokenizeForSimilarity);
3901
3913
  const changedTokens = changedAbs.map(tokenizeForSimilarity);
3902
- const executedTestsAbs = (opts.executedTests ?? []).map((testPath) => path7.resolve(testPath).replace(/\\/g, "/")).filter((absPath) => absPath.length > 0);
3914
+ const executedTestsAbs = (opts.executedTests ?? []).map((testPath) => path8.resolve(testPath).replace(/\\/g, "/")).filter((absPath) => absPath.length > 0);
3903
3915
  const testTokens = executedTestsAbs.map(tokenizeForSimilarity);
3904
- const allMapFilesAbs = opts.map.files().map((absPath) => path7.resolve(absPath).replace(/\\/g, "/"));
3916
+ const allMapFilesAbs = opts.map.files().map((absPath) => path8.resolve(absPath).replace(/\\/g, "/"));
3905
3917
  const uncoveredCandidates = allMapFilesAbs.filter((absPath) => {
3906
3918
  const sum = opts.map.fileCoverageFor(absPath).toSummary();
3907
3919
  return !(sum.lines.pct >= 100 && sum.functions.pct >= 100 && sum.branches.pct >= 100);
@@ -3918,33 +3930,24 @@ var printPerFileCompositeTables = async (opts) => {
3918
3930
  const selectionSetAbs = new Set(selectionAbs);
3919
3931
  const scored = await Promise.all(
3920
3932
  candidates.map(async (abs) => {
3921
- const rel = path7.relative(opts.root, abs).replace(/\\/g, "/");
3933
+ const rel = path8.relative(opts.root, abs).replace(/\\/g, "/");
3922
3934
  const sum = opts.map.fileCoverageFor(abs).toSummary();
3923
3935
  const pct = Number.isFinite(sum.lines.pct) ? sum.lines.pct : 0;
3924
- const absNorm = path7.resolve(abs).replace(/\\/g, "/");
3936
+ const absNorm = path8.resolve(abs).replace(/\\/g, "/");
3925
3937
  const selfTokens = tokenizeForSimilarity(absNorm);
3926
3938
  const selSim = Math.max(
3927
3939
  0,
3928
- ...selectionTokens.map(
3929
- (selectionTokenSet) => jaccard(selfTokens, selectionTokenSet)
3930
- )
3940
+ ...selectionTokens.map((selectionTokenSet) => jaccard(selfTokens, selectionTokenSet))
3931
3941
  );
3932
3942
  const chgSim = Math.max(
3933
3943
  0,
3934
- ...changedTokens.map(
3935
- (changedTokenSet) => jaccard(selfTokens, changedTokenSet)
3936
- )
3937
- );
3938
- const tstSim = Math.max(
3939
- 0,
3940
- ...testTokens.map((tset) => jaccard(selfTokens, tset))
3944
+ ...changedTokens.map((changedTokenSet) => jaccard(selfTokens, changedTokenSet))
3941
3945
  );
3946
+ const tstSim = Math.max(0, ...testTokens.map((tset) => jaccard(selfTokens, tset)));
3942
3947
  const nearSelection = selectionAbs.some(
3943
3948
  (selectionPath) => isSameDirOrChild(absNorm, selectionPath)
3944
3949
  );
3945
- const nearChanged = changedAbs.some(
3946
- (changedPath) => isSameDirOrChild(absNorm, changedPath)
3947
- );
3950
+ const nearChanged = changedAbs.some((changedPath) => isSameDirOrChild(absNorm, changedPath));
3948
3951
  const related = selSim > 0 || chgSim > 0 || nearSelection || nearChanged;
3949
3952
  const distance = selectionSetAbs.has(absNorm) ? 0 : distFromTests.get(absNorm) ?? INF;
3950
3953
  let group = 6;
@@ -3968,9 +3971,12 @@ var printPerFileCompositeTables = async (opts) => {
3968
3971
  return { abs: absNorm, rel, linesPct: pct, group, score, distance };
3969
3972
  })
3970
3973
  );
3971
- let files = scored.sort(
3972
- (left, right) => left.group - right.group || left.distance - right.distance || right.score - left.score || right.linesPct - left.linesPct || left.rel.localeCompare(right.rel)
3973
- ).map((scoredItem) => scoredItem.abs);
3974
+ const prodSeeds = selectionAbs.length > 0 ? selectionAbs : changedAbs;
3975
+ const rank = await computeDirectnessRank({ repoRoot: opts.root, productionSeeds: prodSeeds });
3976
+ let files = sortPathsWithRank(
3977
+ rank,
3978
+ scored.map((s) => s.abs)
3979
+ );
3974
3980
  if (selectionAbs.length > 0) {
3975
3981
  const selectionSet = new Set(selectionAbs);
3976
3982
  const selectedHead = files.filter((filePath) => selectionSet.has(filePath));
@@ -3993,7 +3999,7 @@ var printPerFileCompositeTables = async (opts) => {
3993
3999
  };
3994
4000
 
3995
4001
  // src/lib/jest-bridge.ts
3996
- var path8 = __toESM(require("node:path"), 1);
4002
+ var path9 = __toESM(require("node:path"), 1);
3997
4003
  var fs4 = __toESM(require("node:fs"), 1);
3998
4004
  var util = __toESM(require("node:util"), 1);
3999
4005
  var import_json5 = __toESM(require_lib(), 1);
@@ -4068,7 +4074,7 @@ var extractBridgePath = (raw, cwd) => {
4068
4074
  return null;
4069
4075
  }
4070
4076
  const jsonPath = matches[matches.length - 1][1].trim().replace(/^["'`]|["'`]$/g, "");
4071
- return path8.isAbsolute(jsonPath) ? jsonPath : path8.resolve(cwd, jsonPath);
4077
+ return path9.isAbsolute(jsonPath) ? jsonPath : path9.resolve(cwd, jsonPath);
4072
4078
  };
4073
4079
  var drawRule = (label) => {
4074
4080
  const width = Math.max(
@@ -4667,7 +4673,7 @@ var buildStackSection = (mergedForStack, ctx, fallbackLoc) => {
4667
4673
  const loc = deepestProjectLoc(mergedForStack, ctx.projectHint);
4668
4674
  if (loc) {
4669
4675
  const href = preferredEditorHref(loc.file, loc.line, ctx.editorCmd);
4670
- out.push(` ${ansi.dim("at")} ${osc8(`${path8.basename(loc.file)}:${loc.line}`, href)}`);
4676
+ out.push(` ${ansi.dim("at")} ${osc8(`${path9.basename(loc.file)}:${loc.line}`, href)}`);
4671
4677
  }
4672
4678
  out.push("");
4673
4679
  return out;
@@ -4707,6 +4713,7 @@ var formatJestOutputVitest = (raw, opts) => {
4707
4713
  const projectHint = new RegExp(
4708
4714
  `(${cwd.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})|(/gigworx-node/)`
4709
4715
  );
4716
+ const onlyFailures = Boolean(opts?.onlyFailures);
4710
4717
  const lines = raw.split(/\r?\n/);
4711
4718
  const out = [];
4712
4719
  const seenFailures = /* @__PURE__ */ new Set();
@@ -4794,8 +4801,10 @@ var formatJestOutputVitest = (raw, opts) => {
4794
4801
  continue;
4795
4802
  }
4796
4803
  seenFiles.add(rel);
4797
- const pill = badge === "PASS" ? colorTokens.passPill("PASS") : colorTokens.failPill("FAIL");
4798
- out.push(`${pill} ${ansi.white(rel)}`);
4804
+ if (!(onlyFailures && badge === "PASS")) {
4805
+ const pill = badge === "PASS" ? colorTokens.passPill("PASS") : colorTokens.failPill("FAIL");
4806
+ out.push(`${pill} ${ansi.white(rel)}`);
4807
+ }
4799
4808
  lineIndex += 1;
4800
4809
  continue;
4801
4810
  }
@@ -4908,14 +4917,21 @@ function renderVitestFromJestJSON(data, opts) {
4908
4917
  `(${cwd.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})|(/gigworx-node/)`
4909
4918
  );
4910
4919
  const ctx = { projectHint, editorCmd: opts?.editorCmd, showStacks: true };
4920
+ const onlyFailures = Boolean(opts?.onlyFailures);
4911
4921
  const out = [];
4912
- out.push(renderRunLine(cwd));
4913
- out.push("");
4922
+ if (!onlyFailures) {
4923
+ out.push(renderRunLine(cwd));
4924
+ out.push("");
4925
+ }
4914
4926
  for (const file of data.testResults) {
4915
4927
  const rel = file.testFilePath.replace(/\\/g, "/").replace(`${cwd}/`, "");
4916
4928
  const failed = file.testResults.filter((assertion) => assertion.status === "failed");
4917
- out.push(...buildPerFileOverview(rel, file.testResults));
4918
- out.push(buildFileBadgeLine(rel, failed.length));
4929
+ if (!onlyFailures) {
4930
+ out.push(...buildPerFileOverview(rel, file.testResults));
4931
+ }
4932
+ if (!(onlyFailures && failed.length === 0)) {
4933
+ out.push(buildFileBadgeLine(rel, failed.length));
4934
+ }
4919
4935
  if (file.failureMessage && failed.length === 0) {
4920
4936
  const lines = file.failureMessage.split(/\r?\n/);
4921
4937
  const details = linesFromDetails(file.failureDetails);
@@ -4946,7 +4962,7 @@ function renderVitestFromJestJSON(data, opts) {
4946
4962
  const deepestLoc = deepestProjectLoc(mergedForStack, projectHint);
4947
4963
  const locLink = deepestLoc && (() => {
4948
4964
  const href = preferredEditorHref(deepestLoc.file, deepestLoc.line, opts?.editorCmd);
4949
- const base = `${path8.basename(deepestLoc.file)}:${deepestLoc.line}`;
4965
+ const base = `${path9.basename(deepestLoc.file)}:${deepestLoc.line}`;
4950
4966
  return osc8(base, href);
4951
4967
  })();
4952
4968
  const headerLine = `${ansi.white(header)}${locLink ? ` ${ansi.dim(`(${locLink})`)}` : ""}`;
@@ -4991,13 +5007,8 @@ ${footer}`;
4991
5007
  }
4992
5008
 
4993
5009
  // src/lib/program.ts
4994
- var import_meta = {};
4995
5010
  var jestBin = "./node_modules/.bin/jest";
4996
5011
  var babelNodeBin = "./node_modules/.bin/babel-node";
4997
- var moduleSpecifierForRequire = (
4998
- // @ts-ignore
4999
- typeof __filename !== "undefined" ? __filename : import_meta.url
5000
- );
5001
5012
  var registerSignalHandlersOnce = () => {
5002
5013
  let handled = false;
5003
5014
  const on = (sig) => {
@@ -5015,7 +5026,6 @@ Received ${sig}, exiting...
5015
5026
  };
5016
5027
  var isDebug = () => Boolean(process.env.TEST_CLI_DEBUG);
5017
5028
  var mergeLcov = async () => {
5018
- const jestLcovPath = "coverage/jest/lcov.info";
5019
5029
  const vitestLcovPath = "coverage/vitest/lcov.info";
5020
5030
  const mergedOutPath = "coverage/lcov.info";
5021
5031
  const readOrEmpty = async (filePath) => {
@@ -5026,7 +5036,7 @@ var mergeLcov = async () => {
5026
5036
  }
5027
5037
  };
5028
5038
  let vitestContent = "";
5029
- let jestContent = "";
5039
+ const jestParts = [];
5030
5040
  try {
5031
5041
  vitestContent = await readOrEmpty(vitestLcovPath);
5032
5042
  } catch (readVitestError) {
@@ -5034,20 +5044,46 @@ var mergeLcov = async () => {
5034
5044
  console.info(`read vitest lcov failed: ${String(readVitestError)}`);
5035
5045
  }
5036
5046
  }
5047
+ const collectLcovs = (dir) => {
5048
+ const out = [];
5049
+ try {
5050
+ const entries = fsSync3.readdirSync(dir, { withFileTypes: true });
5051
+ for (const entry of entries) {
5052
+ const full = path10.join(dir, entry.name);
5053
+ if (entry.isDirectory()) {
5054
+ out.push(...collectLcovs(full));
5055
+ } else if (entry.isFile() && entry.name === "lcov.info") {
5056
+ out.push(full);
5057
+ }
5058
+ }
5059
+ } catch {
5060
+ }
5061
+ return out;
5062
+ };
5037
5063
  try {
5038
- jestContent = await readOrEmpty(jestLcovPath);
5064
+ const jestRoot = path10.join("coverage", "jest");
5065
+ const candidates = [path10.join(jestRoot, "lcov.info"), ...collectLcovs(jestRoot)].map((candidatePath) => path10.resolve(candidatePath)).filter((absolutePath, index, arr) => arr.indexOf(absolutePath) === index);
5066
+ for (const filePath of candidates) {
5067
+ try {
5068
+ const content = await readOrEmpty(filePath);
5069
+ if (content.trim()) {
5070
+ jestParts.push(content.trim());
5071
+ }
5072
+ } catch {
5073
+ }
5074
+ }
5039
5075
  } catch (readJestError) {
5040
5076
  if (isDebug()) {
5041
- console.info(`read jest lcov failed: ${String(readJestError)}`);
5077
+ console.info(`scan jest lcov failed: ${String(readJestError)}`);
5042
5078
  }
5043
5079
  }
5044
- if (!vitestContent && !jestContent) {
5080
+ if (!vitestContent && jestParts.length === 0) {
5045
5081
  if (isDebug()) {
5046
5082
  console.info("No coverage outputs found to merge.");
5047
5083
  }
5048
5084
  return;
5049
5085
  }
5050
- const merged = [vitestContent.trim(), jestContent.trim()].filter(Boolean).join("\n");
5086
+ const merged = [vitestContent.trim(), ...jestParts].filter(Boolean).join("\n");
5051
5087
  if (merged.length > 0) {
5052
5088
  await (await import("node:fs/promises")).mkdir("coverage", { recursive: true });
5053
5089
  await (await import("node:fs/promises")).writeFile(mergedOutPath, `${merged}
@@ -5060,23 +5096,40 @@ var mergeLcov = async () => {
5060
5096
  }
5061
5097
  };
5062
5098
  var emitMergedCoverage = async (ui, opts) => {
5063
- const jestJson = path9.join("coverage", "jest", "coverage-final.json");
5064
- const jSize = fsSync3.existsSync(jestJson) ? fsSync3.statSync(jestJson).size : -1;
5065
- const jestSizeLabel = jSize >= 0 ? `${jSize} bytes` : "missing";
5066
- if (isDebug()) {
5067
- console.info(`Coverage JSON probe \u2192 jest: ${jestSizeLabel}`);
5068
- }
5069
- const jestData = await readCoverageJson(jestJson);
5070
- const jestFilesCount = Object.keys(jestData).length;
5071
- if (isDebug()) {
5072
- console.info(`Decoded coverage entries \u2192 jest: ${jestFilesCount}`);
5073
- }
5074
5099
  const map = (0, import_istanbul_lib_coverage2.createCoverageMap)({});
5075
- if (jestFilesCount > 0) {
5100
+ const listJsons = (dir) => {
5101
+ const out = [];
5076
5102
  try {
5077
- map.merge(jestData);
5103
+ const entries = fsSync3.readdirSync(dir, { withFileTypes: true });
5104
+ for (const entry of entries) {
5105
+ const full = path10.join(dir, entry.name);
5106
+ if (entry.isDirectory()) {
5107
+ out.push(...listJsons(full));
5108
+ } else if (entry.isFile() && entry.name === "coverage-final.json") {
5109
+ out.push(full);
5110
+ }
5111
+ }
5112
+ } catch {
5113
+ }
5114
+ return out;
5115
+ };
5116
+ const coverageRoot = path10.join("coverage", "jest");
5117
+ const jsonCandidates = [
5118
+ path10.join(coverageRoot, "coverage-final.json"),
5119
+ ...listJsons(coverageRoot)
5120
+ ].map((candidatePath) => path10.resolve(candidatePath)).filter((absolutePath, index, arr) => {
5121
+ const isFirst = arr.indexOf(absolutePath) === index;
5122
+ const exists = fsSync3.existsSync(absolutePath);
5123
+ return isFirst && exists;
5124
+ });
5125
+ for (const jsonPath of jsonCandidates) {
5126
+ try {
5127
+ const data = await readCoverageJson(jsonPath);
5128
+ if (Object.keys(data).length) {
5129
+ map.merge(data);
5130
+ }
5078
5131
  } catch (mergeJestError) {
5079
- console.warn(`Failed merging jest coverage JSON: ${String(mergeJestError)}`);
5132
+ console.warn(`Failed merging jest coverage JSON @ ${jsonPath}: ${String(mergeJestError)}`);
5080
5133
  }
5081
5134
  }
5082
5135
  if (map.files().length === 0) {
@@ -5126,7 +5179,7 @@ var emitMergedCoverage = async (ui, opts) => {
5126
5179
  executedTests: opts.executedTests ?? []
5127
5180
  });
5128
5181
  const context = LibReport.createContext({
5129
- dir: path9.resolve("coverage", "merged"),
5182
+ dir: path10.resolve("coverage", "merged"),
5130
5183
  coverageMap: filteredMap,
5131
5184
  defaultSummarizer: "nested"
5132
5185
  });
@@ -5194,8 +5247,8 @@ var emitMergedCoverage = async (ui, opts) => {
5194
5247
  for (const reporter of reporters) {
5195
5248
  reporter.execute(context);
5196
5249
  }
5197
- const textPath = path9.resolve("coverage", "merged", "coverage.txt");
5198
- const summaryPath = path9.resolve("coverage", "merged", "coverage-summary.txt");
5250
+ const textPath = path10.resolve("coverage", "merged", "coverage.txt");
5251
+ const summaryPath = path10.resolve("coverage", "merged", "coverage-summary.txt");
5199
5252
  const filesToPrint = [];
5200
5253
  if (fsSync3.existsSync(textPath)) {
5201
5254
  filesToPrint.push(textPath);
@@ -5266,6 +5319,7 @@ var program = async () => {
5266
5319
  collectCoverage,
5267
5320
  coverageUi,
5268
5321
  coverageAbortOnFailure,
5322
+ onlyFailures,
5269
5323
  selectionSpecified,
5270
5324
  selectionPaths,
5271
5325
  includeGlobs,
@@ -5277,17 +5331,43 @@ var program = async () => {
5277
5331
  coverageMode,
5278
5332
  coverageMaxFiles: coverageMaxFilesArg,
5279
5333
  coverageMaxHotspots: coverageMaxHotspotsArg,
5280
- coveragePageFit
5334
+ coveragePageFit,
5335
+ changed
5281
5336
  } = deriveArgs(argv);
5282
- console.info(`Selection \u2192 specified=${selectionSpecified} paths=${selectionPaths.length}`);
5337
+ const getChangedFiles = async (mode, cwd) => {
5338
+ const collect = async (cmd, args) => {
5339
+ try {
5340
+ const out = await runText(cmd, args, {
5341
+ cwd,
5342
+ env: safeEnv(process.env, {}),
5343
+ timeoutMs: 4e3
5344
+ });
5345
+ return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
5346
+ } catch {
5347
+ return [];
5348
+ }
5349
+ };
5350
+ const staged = mode === "staged" || mode === "all" ? await collect("git", ["diff", "--name-only", "--diff-filter=ACMRTUXB", "--cached"]) : [];
5351
+ const unstagedTracked = mode === "unstaged" || mode === "all" ? await collect("git", ["diff", "--name-only", "--diff-filter=ACMRTUXB"]) : [];
5352
+ const untracked = mode === "unstaged" || mode === "all" ? await collect("git", ["ls-files", "--others", "--exclude-standard"]) : [];
5353
+ const rels = Array.from(/* @__PURE__ */ new Set([...staged, ...unstagedTracked, ...untracked]));
5354
+ return rels.map((rel) => path10.resolve(cwd, rel).replace(/\\/g, "/")).filter((abs) => !abs.includes("/node_modules/") && !abs.includes("/coverage/"));
5355
+ };
5356
+ const repoRootForChanged = workspaceRoot ?? await findRepoRoot();
5357
+ const changedSelectionAbs = changed ? await getChangedFiles(changed, repoRootForChanged) : [];
5358
+ const selectionPathsAugmented = changedSelectionAbs.length ? Array.from(/* @__PURE__ */ new Set([...selectionPaths, ...changedSelectionAbs])) : selectionPaths;
5359
+ const selectionSpecifiedAugmented = Boolean(selectionSpecified || changedSelectionAbs.length > 0);
5360
+ console.info(
5361
+ `Selection \u2192 specified=${selectionSpecifiedAugmented} paths=${selectionPathsAugmented.length}`
5362
+ );
5283
5363
  const { jest } = argsForDiscovery(["run"], jestArgs);
5284
- const selectionLooksLikeTest = selectionPaths.some(
5364
+ const selectionLooksLikeTest = selectionPathsAugmented.some(
5285
5365
  (pathText) => /\.(test|spec)\.[tj]sx?$/i.test(pathText) || /(^|\/)tests?\//i.test(pathText)
5286
5366
  );
5287
- const selectionLooksLikePath = selectionPaths.some(
5367
+ const selectionLooksLikePath = selectionPathsAugmented.some(
5288
5368
  (pathText) => /[\\/]/.test(pathText) || /\.(m?[tj]sx?)$/i.test(pathText)
5289
5369
  );
5290
- const selectionHasPaths = selectionPaths.length > 0;
5370
+ const selectionHasPaths = selectionPathsAugmented.length > 0;
5291
5371
  const repoRootForDiscovery = workspaceRoot ?? await findRepoRoot();
5292
5372
  const expandProductionSelections = async (tokens, repoRoot) => {
5293
5373
  const results = /* @__PURE__ */ new Set();
@@ -5296,18 +5376,18 @@ var program = async () => {
5296
5376
  if (!token) {
5297
5377
  continue;
5298
5378
  }
5299
- const isAbs = path9.isAbsolute(token);
5379
+ const isAbs = path10.isAbsolute(token);
5300
5380
  const looksLikeRelPath = /[\\/]/.test(token);
5301
5381
  let candidateFromRoot;
5302
5382
  if (token.startsWith("/")) {
5303
- candidateFromRoot = path9.join(repoRoot, token.slice(1));
5383
+ candidateFromRoot = path10.join(repoRoot, token.slice(1));
5304
5384
  } else if (looksLikeRelPath) {
5305
- candidateFromRoot = path9.join(repoRoot, token);
5385
+ candidateFromRoot = path10.join(repoRoot, token);
5306
5386
  } else {
5307
5387
  candidateFromRoot = void 0;
5308
5388
  }
5309
5389
  const tryPushIfProd = (absPath) => {
5310
- const norm = path9.resolve(absPath).replace(/\\/g, "/");
5390
+ const norm = path10.resolve(absPath).replace(/\\/g, "/");
5311
5391
  const isTest = /(^|\/)tests?\//i.test(norm) || /\.(test|spec)\.[tj]sx?$/i.test(norm);
5312
5392
  if (!isTest && fsSync3.existsSync(norm)) {
5313
5393
  results.add(norm);
@@ -5329,7 +5409,7 @@ var program = async () => {
5329
5409
  }),
5330
5410
  timeoutMs: 4e3
5331
5411
  });
5332
- const matches = out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((rel) => path9.resolve(repoRoot, rel).replace(/\\/g, "/")).filter(
5412
+ const matches = out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((rel) => path10.resolve(repoRoot, rel).replace(/\\/g, "/")).filter(
5333
5413
  (abs) => !abs.includes("/node_modules/") && !abs.includes("/coverage/") && !/(^|\/)tests?\//i.test(abs) && !/\.(test|spec)\.[tj]sx?$/i.test(abs)
5334
5414
  );
5335
5415
  matches.forEach((abs) => results.add(abs));
@@ -5338,20 +5418,20 @@ var program = async () => {
5338
5418
  }
5339
5419
  return Array.from(results);
5340
5420
  };
5341
- const initialProdSelections = selectionPaths.filter(
5421
+ const initialProdSelections = selectionPathsAugmented.filter(
5342
5422
  (pathText) => (/[\\/]/.test(pathText) || /\.(m?[tj]sx?)$/i.test(pathText)) && !/(^|\/)tests?\//i.test(pathText) && !/\.(test|spec)\.[tj]sx?$/i.test(pathText)
5343
5423
  );
5344
- const expandedProdSelections = initialProdSelections.length ? initialProdSelections : await expandProductionSelections(selectionPaths, repoRootForDiscovery);
5424
+ const expandedProdSelections = initialProdSelections.length ? initialProdSelections : await expandProductionSelections(selectionPathsAugmented, repoRootForDiscovery);
5345
5425
  const selectionIncludesProdPaths = expandedProdSelections.length > 0;
5346
5426
  console.info(
5347
5427
  `Selection classify \u2192 looksLikePath=${selectionLooksLikePath} looksLikeTest=${selectionLooksLikeTest} prodPaths=${selectionIncludesProdPaths}`
5348
5428
  );
5349
- const stripPathTokens = (args) => args.filter((token) => !selectionPaths.includes(token));
5429
+ const stripPathTokens = (args) => args.filter((token) => !selectionPathsAugmented.includes(token));
5350
5430
  const jestDiscoveryArgs = selectionIncludesProdPaths ? stripPathTokens(jest) : jest;
5351
5431
  const projectConfigs = [];
5352
5432
  try {
5353
- const baseCfg = path9.resolve("jest.config.js");
5354
- const tsCfg = path9.resolve("jest.ts.config.js");
5433
+ const baseCfg = path10.resolve("jest.config.js");
5434
+ const tsCfg = path10.resolve("jest.ts.config.js");
5355
5435
  if (fsSync3.existsSync(baseCfg)) {
5356
5436
  projectConfigs.push(baseCfg);
5357
5437
  }
@@ -5368,7 +5448,7 @@ var program = async () => {
5368
5448
  );
5369
5449
  const prodSelections2 = expandedProdSelections;
5370
5450
  for (const cfg of projectConfigs) {
5371
- const cfgCwd = path9.dirname(cfg);
5451
+ const cfgCwd = path10.dirname(cfg);
5372
5452
  const allTests = await discoverJestResilient([...jestDiscoveryArgs, "--config", cfg], {
5373
5453
  cwd: cfgCwd
5374
5454
  });
@@ -5381,7 +5461,7 @@ var program = async () => {
5381
5461
  });
5382
5462
  } catch (err) {
5383
5463
  if (isDebug()) {
5384
- console.warn(`direct selection failed for project ${path9.basename(cfg)}: ${String(err)}`);
5464
+ console.warn(`direct selection failed for project ${path10.basename(cfg)}: ${String(err)}`);
5385
5465
  }
5386
5466
  }
5387
5467
  perProjectFiles.set(cfg, directPerProject);
@@ -5393,7 +5473,7 @@ var program = async () => {
5393
5473
  )} | related=${selectionIncludesProdPaths} | cwd=${repoRootForDiscovery}`
5394
5474
  );
5395
5475
  for (const cfg of projectConfigs) {
5396
- const cfgCwd = path9.dirname(cfg);
5476
+ const cfgCwd = path10.dirname(cfg);
5397
5477
  const files = await discoverJestResilient([...jestDiscoveryArgs, "--config", cfg], {
5398
5478
  cwd: cfgCwd
5399
5479
  });
@@ -5403,18 +5483,18 @@ var program = async () => {
5403
5483
  const perProjectFiltered = /* @__PURE__ */ new Map();
5404
5484
  for (const cfg of projectConfigs) {
5405
5485
  const files = perProjectFiles.get(cfg) ?? [];
5406
- const selectionTestPaths = selectionPaths.filter(
5486
+ const selectionTestPaths = selectionPathsAugmented.filter(
5407
5487
  (pathToken) => /\.(test|spec)\.[tj]sx?$/i.test(pathToken) || /(^|\/)tests?\//i.test(pathToken)
5408
5488
  );
5409
5489
  const candidates = selectionHasPaths && selectionLooksLikeTest ? selectionTestPaths : files;
5410
5490
  const absFiles = candidates.map(
5411
- (candidatePath) => path9.isAbsolute(candidatePath) ? candidatePath : path9.join(repoRootForDiscovery, candidatePath)
5491
+ (candidatePath) => path10.isAbsolute(candidatePath) ? candidatePath : path10.join(repoRootForDiscovery, candidatePath)
5412
5492
  ).map((absolutePath) => absolutePath.replace(/\\/g, "/"));
5413
5493
  const onlyOwned = await filterCandidatesForProject(
5414
5494
  cfg,
5415
5495
  jestDiscoveryArgs,
5416
5496
  absFiles,
5417
- path9.dirname(cfg)
5497
+ path10.dirname(cfg)
5418
5498
  );
5419
5499
  perProjectFiltered.set(cfg, onlyOwned);
5420
5500
  }
@@ -5426,7 +5506,7 @@ var program = async () => {
5426
5506
  if (selectionHasPaths && prodSelections.length > 0) {
5427
5507
  console.info(`rg related \u2192 prodSelections=${prodSelections.length} (starting)`);
5428
5508
  const repoRootForRefinement = workspaceRoot ?? await findRepoRoot();
5429
- const selectionKey = prodSelections.map((absPath) => path9.relative(repoRootForRefinement, absPath).replace(/\\/g, "/")).sort((a, b) => a.localeCompare(b)).join("|");
5509
+ const selectionKey = prodSelections.map((absPath) => path10.relative(repoRootForRefinement, absPath).replace(/\\/g, "/")).sort((firstPath, secondPath) => firstPath.localeCompare(secondPath)).join("|");
5430
5510
  const { cachedRelated: cachedRelated2, findRelatedTestsFast: findRelatedTestsFast2, DEFAULT_TEST_GLOBS: DEFAULT_TEST_GLOBS2 } = await Promise.resolve().then(() => (init_fast_related(), fast_related_exports));
5431
5511
  const { DEFAULT_EXCLUDE: DEFAULT_EXCLUDE2 } = await Promise.resolve().then(() => (init_args(), args_exports));
5432
5512
  const rgMatches = await cachedRelated2({
@@ -5456,7 +5536,7 @@ var program = async () => {
5456
5536
  cfg,
5457
5537
  jestDiscoveryArgs,
5458
5538
  rgCandidates,
5459
- path9.dirname(cfg)
5539
+ path10.dirname(cfg)
5460
5540
  );
5461
5541
  perProjectFromRg.set(cfg, owned);
5462
5542
  }
@@ -5471,9 +5551,9 @@ var program = async () => {
5471
5551
  } else {
5472
5552
  const repoRootForScan = repoRootForDiscovery;
5473
5553
  const toSeeds = (abs) => {
5474
- const rel = path9.relative(repoRootForScan, abs).replace(/\\/g, "/");
5554
+ const rel = path10.relative(repoRootForScan, abs).replace(/\\/g, "/");
5475
5555
  const withoutExt = rel.replace(/\.(m?[tj]sx?)$/i, "");
5476
- const base = path9.basename(withoutExt);
5556
+ const base = path10.basename(withoutExt);
5477
5557
  const segs = withoutExt.split("/");
5478
5558
  const tail2 = segs.slice(-2).join("/");
5479
5559
  return Array.from(new Set([withoutExt, base, tail2].filter(Boolean)));
@@ -5488,8 +5568,8 @@ var program = async () => {
5488
5568
  }
5489
5569
  };
5490
5570
  const resolveLocalImport = (fromFile, spec) => {
5491
- const baseDir = path9.dirname(fromFile);
5492
- const cand = path9.resolve(baseDir, spec);
5571
+ const baseDir = path10.dirname(fromFile);
5572
+ const cand = path10.resolve(baseDir, spec);
5493
5573
  const exts = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
5494
5574
  for (const ext of exts) {
5495
5575
  const full = ext ? `${cand}${ext}` : cand;
@@ -5498,7 +5578,7 @@ var program = async () => {
5498
5578
  }
5499
5579
  }
5500
5580
  for (const ext of exts) {
5501
- const full = path9.join(cand, `index${ext}`);
5581
+ const full = path10.join(cand, `index${ext}`);
5502
5582
  if (fsSync3.existsSync(full)) {
5503
5583
  return full;
5504
5584
  }
@@ -5552,7 +5632,7 @@ var program = async () => {
5552
5632
  cfg,
5553
5633
  jestDiscoveryArgs,
5554
5634
  keptCandidates,
5555
- path9.dirname(cfg)
5635
+ path10.dirname(cfg)
5556
5636
  );
5557
5637
  perProjectFromScan.set(cfg, owned);
5558
5638
  }
@@ -5579,9 +5659,9 @@ var program = async () => {
5579
5659
  if (effectiveJestFiles.length === 0) {
5580
5660
  const repoRoot = repoRootForRefinement;
5581
5661
  const seeds = prodSelections.map(
5582
- (abs) => path9.relative(repoRoot, abs).replace(/\\/g, "/").replace(/\.(m?[tj]sx?)$/i, "")
5662
+ (abs) => path10.relative(repoRoot, abs).replace(/\\/g, "/").replace(/\.(m?[tj]sx?)$/i, "")
5583
5663
  ).flatMap((rel) => {
5584
- const base = path9.basename(rel);
5664
+ const base = path10.basename(rel);
5585
5665
  const segments = rel.split("/");
5586
5666
  return Array.from(new Set([rel, base, segments.slice(-2).join("/")].filter(Boolean)));
5587
5667
  });
@@ -5594,8 +5674,8 @@ var program = async () => {
5594
5674
  }
5595
5675
  };
5596
5676
  const resolveLocalImport = (fromFile, spec) => {
5597
- const baseDir = path9.dirname(fromFile);
5598
- const candidate = path9.resolve(baseDir, spec);
5677
+ const baseDir = path10.dirname(fromFile);
5678
+ const candidate = path10.resolve(baseDir, spec);
5599
5679
  const extensions = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
5600
5680
  for (const ext of extensions) {
5601
5681
  const fullPath = ext ? `${candidate}${ext}` : candidate;
@@ -5604,7 +5684,7 @@ var program = async () => {
5604
5684
  }
5605
5685
  }
5606
5686
  for (const ext of extensions) {
5607
- const fullPath = path9.join(candidate, `index${ext}`);
5687
+ const fullPath = path10.join(candidate, `index${ext}`);
5608
5688
  if (fsSync3.existsSync(fullPath)) {
5609
5689
  return fullPath;
5610
5690
  }
@@ -5723,8 +5803,8 @@ var program = async () => {
5723
5803
  }
5724
5804
  }
5725
5805
  const jestDecision = decideShouldRunJest([], effectiveJestFiles, {
5726
- selectionSpecified,
5727
- selectionPaths
5806
+ selectionSpecified: selectionSpecifiedAugmented,
5807
+ selectionPaths: selectionPathsAugmented
5728
5808
  });
5729
5809
  const { shouldRunJest } = jestDecision;
5730
5810
  const jestCount = effectiveJestFiles.length;
@@ -5742,45 +5822,63 @@ var program = async () => {
5742
5822
  }
5743
5823
  console.info(`Run plan \u2192 Jest maybe=${shouldRunJest} (projects=${projectConfigs.length})`);
5744
5824
  let jestExitCode = 0;
5825
+ const allBridgeJson = [];
5745
5826
  const executedTestFilesSet = /* @__PURE__ */ new Set();
5746
5827
  if (shouldRunJest) {
5747
5828
  console.info("Starting Jest (no Vitest targets)\u2026");
5748
5829
  await runJestBootstrap();
5749
5830
  const jestRunArgs = selectionIncludesProdPaths ? stripPathTokens(jestArgs) : jestArgs;
5831
+ const sanitizedJestRunArgs = jestRunArgs.filter(
5832
+ (arg) => !/^--coverageDirectory(?:=|$)/.test(String(arg))
5833
+ );
5750
5834
  const projectsToRun = projectConfigs.filter(
5751
5835
  (cfg) => (perProjectFiltered.get(cfg) ?? []).length > 0
5752
5836
  );
5753
- const totalProjectsToRun = projectsToRun.length;
5754
5837
  const stripFooter = (text) => {
5755
5838
  const lines = text.split("\n");
5756
5839
  const idx = lines.findIndex((ln) => /^Test Files\s/.test(stripAnsiSimple(ln)));
5757
5840
  return idx >= 0 ? lines.slice(0, idx).join("\n").trimEnd() : text;
5758
5841
  };
5842
+ const prodSeedsForRun = (() => {
5843
+ const changedAbs = (changedSelectionAbs ?? []).map(
5844
+ (absPath) => path10.resolve(absPath).replace(/\\/g, "/")
5845
+ );
5846
+ const selAbs = selectionPathsAugmented.map(
5847
+ (pathToken) => path10.resolve(pathToken).replace(/\\/g, "/")
5848
+ );
5849
+ return (changedAbs.length ? changedAbs : selAbs).filter(
5850
+ (abs) => /[\\/]/.test(abs) && !/(^|\/)tests?\//i.test(abs) && !/\.(test|spec)\.[tj]sx?$/i.test(abs)
5851
+ );
5852
+ })();
5853
+ const repoRootForRank = repoRootForDiscovery;
5854
+ const fileRank = await computeDirectnessRank({
5855
+ repoRoot: repoRootForRank,
5856
+ productionSeeds: prodSeedsForRun
5857
+ });
5759
5858
  for (let projIndex = 0; projIndex < projectsToRun.length; projIndex += 1) {
5760
5859
  const cfg = projectsToRun[projIndex];
5761
- const isLastProject = projIndex === totalProjectsToRun - 1;
5762
5860
  const files = perProjectFiltered.get(cfg) ?? [];
5763
5861
  if (files.length === 0) {
5764
- console.info(`Project ${path9.basename(cfg)}: 0 matching tests after filter; skipping.`);
5862
+ console.info(`Project ${path10.basename(cfg)}: 0 matching tests after filter; skipping.`);
5765
5863
  continue;
5766
5864
  }
5767
5865
  files.forEach(
5768
- (absTestPath) => executedTestFilesSet.add(path9.resolve(absTestPath).replace(/\\/g, "/"))
5866
+ (absTestPath) => executedTestFilesSet.add(path10.resolve(absTestPath).replace(/\\/g, "/"))
5769
5867
  );
5770
- const outJson = path9.join(
5868
+ const outJson = path10.join(
5771
5869
  os2.tmpdir(),
5772
5870
  `jest-bridge-${Date.now()}-${Math.random().toString(36).slice(2)}.json`
5773
5871
  );
5774
- const reporterPath = path9.resolve("scripts/jest-vitest-bridge.cjs");
5872
+ const reporterPath = path10.resolve("scripts/jest-vitest-bridge.cjs");
5775
5873
  try {
5776
5874
  if (!fsSync3.existsSync(reporterPath)) {
5777
- fsSync3.mkdirSync(path9.dirname(reporterPath), { recursive: true });
5875
+ fsSync3.mkdirSync(path10.dirname(reporterPath), { recursive: true });
5778
5876
  fsSync3.writeFileSync(reporterPath, JEST_BRIDGE_REPORTER_SOURCE, "utf8");
5779
5877
  }
5780
5878
  } catch (ensureReporterError) {
5781
5879
  console.warn(`Unable to ensure jest bridge reporter: ${String(ensureReporterError)}`);
5782
5880
  }
5783
- const selectedFilesForCoverage = selectionPaths.filter((pathToken) => /[\\/]/.test(pathToken)).filter((pathToken) => !looksLikeTestPath(pathToken)).map((pathToken) => path9.relative(repoRootForDiscovery, pathToken).replace(/\\\\/g, "/")).filter((rel) => rel && !/^\.+\//.test(rel)).map((rel) => rel.startsWith("./") ? rel : `./${rel}`);
5881
+ const selectedFilesForCoverage = selectionPathsAugmented.filter((pathToken) => /[\\/]/.test(pathToken)).filter((pathToken) => !looksLikeTestPath(pathToken)).map((pathToken) => path10.relative(repoRootForDiscovery, pathToken).replace(/\\\\/g, "/")).filter((rel) => rel && !/^\.+\//.test(rel)).map((rel) => rel.startsWith("./") ? rel : `./${rel}`);
5784
5882
  const coverageFromArgs = [];
5785
5883
  for (const relPath of selectedFilesForCoverage) {
5786
5884
  coverageFromArgs.push("--collectCoverageFrom", relPath);
@@ -5794,14 +5892,17 @@ var program = async () => {
5794
5892
  "--config",
5795
5893
  cfg,
5796
5894
  "--runTestsByPath",
5797
- "--reporters",
5798
- reporterPath,
5895
+ `--reporters=${reporterPath}`,
5799
5896
  "--silent",
5800
5897
  "--colors",
5801
5898
  "--json",
5802
5899
  "--outputFile",
5803
5900
  outJson,
5804
- ...jestRunArgs,
5901
+ ...sanitizedJestRunArgs,
5902
+ ...collectCoverage ? [
5903
+ "--coverageDirectory",
5904
+ path10.join("coverage", "jest", path10.basename(cfg).replace(/[^a-zA-Z0-9_.-]+/g, "_"))
5905
+ ] : [],
5805
5906
  ...coverageFromArgs,
5806
5907
  "--passWithNoTests",
5807
5908
  ...files
@@ -5827,10 +5928,24 @@ var program = async () => {
5827
5928
  const jsonText = fsSync3.readFileSync(outJson, "utf8");
5828
5929
  const parsed = JSON.parse(jsonText);
5829
5930
  const bridge = coerceJestJsonToBridge(parsed);
5830
- pretty = renderVitestFromJestJSON(bridge, {
5831
- cwd: repoRootForDiscovery,
5832
- ...editorCmd !== void 0 ? { editorCmd } : {}
5833
- });
5931
+ allBridgeJson.push(bridge);
5932
+ try {
5933
+ const reordered = {
5934
+ ...bridge,
5935
+ testResults: sortTestResultsWithRank(fileRank, bridge.testResults).reverse()
5936
+ };
5937
+ pretty = renderVitestFromJestJSON(reordered, {
5938
+ cwd: repoRootForDiscovery,
5939
+ ...editorCmd !== void 0 ? { editorCmd } : {},
5940
+ onlyFailures
5941
+ });
5942
+ } catch {
5943
+ pretty = renderVitestFromJestJSON(bridge, {
5944
+ cwd: repoRootForDiscovery,
5945
+ ...editorCmd !== void 0 ? { editorCmd } : {},
5946
+ onlyFailures
5947
+ });
5948
+ }
5834
5949
  if (debug) {
5835
5950
  const preview = pretty.split("\n").slice(0, 3).join("\n");
5836
5951
  console.info(`pretty preview (json):
@@ -5845,7 +5960,8 @@ ${preview}${pretty.includes("\n") ? "\n\u2026" : ""}`);
5845
5960
  }
5846
5961
  const renderOpts = {
5847
5962
  cwd: repoRootForDiscovery,
5848
- ...editorCmd !== void 0 ? { editorCmd } : {}
5963
+ ...editorCmd !== void 0 ? { editorCmd } : {},
5964
+ onlyFailures
5849
5965
  };
5850
5966
  pretty = formatJestOutputVitest(output, renderOpts);
5851
5967
  if (debug) {
@@ -5854,9 +5970,7 @@ ${preview}${pretty.includes("\n") ? "\n\u2026" : ""}`);
5854
5970
  ${preview}${pretty.includes("\n") ? "\n\u2026" : ""}`);
5855
5971
  }
5856
5972
  }
5857
- if (!isLastProject) {
5858
- pretty = stripFooter(pretty);
5859
- }
5973
+ pretty = stripFooter(pretty);
5860
5974
  if (pretty.trim().length > 0) {
5861
5975
  process.stdout.write(pretty.endsWith("\n") ? pretty : `${pretty}
5862
5976
  `);
@@ -5868,15 +5982,70 @@ ${preview}${pretty.includes("\n") ? "\n\u2026" : ""}`);
5868
5982
  } else {
5869
5983
  console.info("Jest run skipped based on selection and thresholds.");
5870
5984
  }
5871
- if (collectCoverage && shouldRunJest && coverageAbortOnFailure && jestExitCode !== 0) {
5872
- process.exit(jestExitCode);
5985
+ if (allBridgeJson.length > 0) {
5986
+ const agg = allBridgeJson.map((bridge) => bridge.aggregated);
5987
+ const sum = (select) => agg.reduce((total, item) => total + (select(item) || 0), 0);
5988
+ const startTime = Math.min(
5989
+ ...allBridgeJson.map((bridge) => Number(bridge.startTime || Date.now()))
5990
+ );
5991
+ const unified = {
5992
+ startTime,
5993
+ testResults: allBridgeJson.flatMap((bridge) => bridge.testResults),
5994
+ aggregated: {
5995
+ numTotalTestSuites: sum((item) => item.numTotalTestSuites),
5996
+ numPassedTestSuites: sum((item) => item.numPassedTestSuites),
5997
+ numFailedTestSuites: sum((item) => item.numFailedTestSuites),
5998
+ numTotalTests: sum((item) => item.numTotalTests),
5999
+ numPassedTests: sum((item) => item.numPassedTests),
6000
+ numFailedTests: sum((item) => item.numFailedTests),
6001
+ numPendingTests: sum((item) => item.numPendingTests),
6002
+ numTodoTests: sum((item) => item.numTodoTests),
6003
+ startTime,
6004
+ success: agg.every((item) => Boolean(item.success)),
6005
+ runTimeMs: sum((item) => Number(item.runTimeMs ?? 0))
6006
+ }
6007
+ };
6008
+ try {
6009
+ const prodSeeds = (() => {
6010
+ const changedAbs = (changedSelectionAbs ?? []).map(
6011
+ (absPath) => path10.resolve(absPath).replace(/\\/g, "/")
6012
+ );
6013
+ const selAbs = selectionPathsAugmented.map(
6014
+ (pathToken) => path10.resolve(pathToken).replace(/\\/g, "/")
6015
+ );
6016
+ return (changedAbs.length ? changedAbs : selAbs).filter(
6017
+ (abs) => /[\\/]/.test(abs) && !/(^|\/)tests?\//i.test(abs) && !/\.(test|spec)\.[tj]sx?$/i.test(abs)
6018
+ );
6019
+ })();
6020
+ const unifiedRank = await computeDirectnessRank({
6021
+ repoRoot: repoRootForDiscovery,
6022
+ productionSeeds: prodSeeds
6023
+ });
6024
+ const ordered = sortTestResultsWithRank(unifiedRank, unified.testResults).reverse();
6025
+ unified.testResults = ordered;
6026
+ } catch {
6027
+ }
6028
+ const text = renderVitestFromJestJSON(unified, {
6029
+ cwd: repoRootForDiscovery,
6030
+ ...editorCmd !== void 0 ? { editorCmd } : {},
6031
+ onlyFailures
6032
+ });
6033
+ if (text.trim().length > 0) {
6034
+ process.stdout.write(text.endsWith("\n") ? text : `${text}
6035
+ `);
6036
+ }
6037
+ }
6038
+ const finalExitCode = jestExitCode;
6039
+ if (collectCoverage && shouldRunJest && coverageAbortOnFailure && finalExitCode !== 0) {
6040
+ process.exit(finalExitCode);
6041
+ return;
5873
6042
  }
5874
6043
  if (collectCoverage && shouldRunJest) {
5875
6044
  await mergeLcov();
5876
6045
  const repoRoot = workspaceRoot ?? await findRepoRoot();
5877
6046
  const mergedOptsBase = {
5878
- selectionSpecified,
5879
- selectionPaths,
6047
+ selectionSpecified: selectionSpecifiedAugmented,
6048
+ selectionPaths: selectionPathsAugmented,
5880
6049
  includeGlobs,
5881
6050
  excludeGlobs,
5882
6051
  workspaceRoot: repoRoot,
@@ -5891,7 +6060,6 @@ ${preview}${pretty.includes("\n") ? "\n\u2026" : ""}`);
5891
6060
  };
5892
6061
  await emitMergedCoverage(coverageUi, mergedOptsBase);
5893
6062
  }
5894
- const finalExitCode = jestExitCode;
5895
6063
  process.exit(finalExitCode);
5896
6064
  };
5897
6065