@dev-pi2pie/word-counter 0.1.0 → 0.1.2

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
@@ -121,17 +121,25 @@ word-counter --path ./examples/test-case-multi-files-support --keep-progress
121
121
 
122
122
  Progress is transient by default, auto-disabled for single-input runs, and suppressed in `--format raw` and `--format json`.
123
123
 
124
- ### Stable Path Resolution Contract (`#26`)
124
+ ### Stable Path Resolution Contract
125
125
 
126
126
  - Repeated `--path` values are accepted as mixed inputs (file + directory).
127
127
  - In `--path-mode auto` (default), directory inputs are expanded to files (recursive unless `--no-recursive`).
128
- - In `--path-mode manual`, directory inputs are not expanded and are skipped as non-regular files.
129
- - Extension filters apply only to files discovered from directory expansion.
130
- - Direct file inputs are always considered regardless of `--include-ext` / `--exclude-ext`.
128
+ - In `--path-mode manual`, `--path` values are treated as literal file inputs; `--path <dir>` is not supported and is skipped as `not a regular file`.
129
+ - Extension and regex filters apply only to files discovered from directory expansion.
130
+ - Direct file inputs are always considered regardless of `--include-ext` / `--exclude-ext` / `--regex`.
131
131
  - Overlap dedupe is by resolved absolute file path.
132
132
  - If the same file is discovered multiple ways (repeated roots, nested roots, explicit file + directory), it is counted once.
133
133
  - Final processing order is deterministic: resolved files are sorted by absolute path ascending before load/count.
134
134
 
135
+ Path mode examples:
136
+
137
+ ```bash
138
+ word-counter --path ./examples/test-case-multi-files-support --path-mode auto
139
+ word-counter --path ./examples/test-case-multi-files-support --path-mode manual
140
+ word-counter --path ./examples/test-case-multi-files-support/a.md --path-mode manual
141
+ ```
142
+
135
143
  ### Extension Filters
136
144
 
137
145
  Use include/exclude filters for directory scans:
@@ -147,6 +155,28 @@ Direct file path example (filters do not block explicit file inputs):
147
155
  word-counter --path ./examples/test-case-multi-files-support/ignored.js --include-ext .md --exclude-ext .md
148
156
  ```
149
157
 
158
+ ### Regex Filter (`--regex`)
159
+
160
+ Use `--regex` to include only directory-scanned files whose root-relative path matches:
161
+
162
+ ```bash
163
+ word-counter --path ./examples/test-case-multi-files-support --regex '^a\\.md$'
164
+ word-counter --path ./examples/test-case-multi-files-support --regex '^nested/.*\\.md$'
165
+ word-counter --path ./examples/test-case-multi-files-support --path ./examples --regex '\\.md$'
166
+ ```
167
+
168
+ Regex behavior contract:
169
+
170
+ - `--regex` applies only to files discovered from `--path <dir>` expansion.
171
+ - Matching is against each directory root-relative path.
172
+ - The same regex is applied across all provided directory roots.
173
+ - Direct file inputs are literal and are not blocked by regex filters.
174
+ - In `--path-mode manual`, directories are not expanded, so `--include-ext`, `--exclude-ext`, and `--regex` have no effect.
175
+ - `--regex` is single-use; repeated `--regex` flags fail fast with a misuse error.
176
+ - Empty regex values are treated as no regex restriction.
177
+
178
+ For additional usage details and troubleshooting, see [`docs/regex-usage-guide.md`](docs/regex-usage-guide.md).
179
+
150
180
  ### Debugging Diagnostics (`--debug`)
151
181
 
152
182
  `--debug` remains the diagnostics gate and now defaults to `compact` event volume:
@@ -426,6 +456,9 @@ Select how results are printed with `--format`:
426
456
  - `raw` – only the total count (single number).
427
457
  - `json` – machine-readable output; add `--pretty` for indentation.
428
458
 
459
+ JSON contract reference:
460
+ - `docs/schemas/json-output-contract.md`
461
+
429
462
  Examples:
430
463
 
431
464
  ```bash
@@ -465,7 +498,13 @@ Rules:
465
498
  - `symbol` -> `symbols`
466
499
  - `punction` -> `punctuation`
467
500
 
468
- JSON output adds `meta.totalOf` and `meta.totalOfOverride` when `--total-of` is provided.
501
+ JSON output adds override metadata when `--total-of` is provided:
502
+
503
+ - single input and merged batch: `meta.totalOf`, `meta.totalOfOverride`
504
+ - per-file batch (`--per-file`):
505
+ - top-level: `meta.totalOf`, `meta.aggregateTotalOfOverride`
506
+ - per entry: `files[i].meta.totalOf`, `files[i].meta.totalOfOverride`
507
+ - applies to both sectioned and non-sectioned per-file JSON results
469
508
 
470
509
  Example JSON (trimmed):
471
510
 
package/dist/esm/bin.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { Command, Option } from "commander";
4
4
  import { closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, statSync } from "node:fs";
5
- import { basename, dirname, extname, join, relative, resolve } from "node:path";
5
+ import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { readFile, readdir, stat } from "node:fs/promises";
8
8
  import { parseDocument } from "yaml";
@@ -55,8 +55,9 @@ function withCollisionSuffix(pathValue, sequence) {
55
55
  function resolveReportPath(report, now, pid) {
56
56
  const cwd = report.cwd ?? process.cwd();
57
57
  const defaultName = `wc-debug-${formatDebugReportTimestamp(now)}-${pid}.jsonl`;
58
- const explicitPath = typeof report.path === "string";
59
- const basePath = explicitPath ? resolve(cwd, report.path) : resolve(cwd, defaultName);
58
+ const explicitPathValue = typeof report.path === "string" ? report.path : void 0;
59
+ const explicitPath = explicitPathValue !== void 0;
60
+ const basePath = resolve(cwd, explicitPathValue ?? defaultName);
60
61
  mkdirSync(dirname(basePath), { recursive: true });
61
62
  if (explicitPath) {
62
63
  if (existsSync(basePath) && statSync(basePath).isDirectory()) throw new Error(`debug report path must be a file: ${basePath}`);
@@ -191,6 +192,34 @@ function shouldIncludeFromDirectory(filePath, filter) {
191
192
  const extension = extname(filePath).toLowerCase();
192
193
  return filter.effectiveIncludeExtensions.has(extension);
193
194
  }
195
+ function buildDirectoryRegexFilter(pattern) {
196
+ if (pattern === void 0) return {
197
+ sourcePattern: void 0,
198
+ regex: void 0
199
+ };
200
+ if (pattern.trim().length === 0) return {
201
+ sourcePattern: pattern,
202
+ regex: void 0
203
+ };
204
+ try {
205
+ return {
206
+ sourcePattern: pattern,
207
+ regex: new RegExp(pattern)
208
+ };
209
+ } catch (error) {
210
+ const message = error instanceof Error ? error.message : String(error);
211
+ throw new Error(`Invalid --regex pattern: ${message}`);
212
+ }
213
+ }
214
+ function toDirectoryRelativePath(rootPath, filePath) {
215
+ const relativePath = relative(rootPath, filePath);
216
+ if (sep === "/") return relativePath;
217
+ return relativePath.split(sep).join("/");
218
+ }
219
+ function shouldIncludeFromDirectoryRegex(relativePath, filter) {
220
+ if (!filter.regex) return true;
221
+ return filter.regex.test(relativePath);
222
+ }
194
223
 
195
224
  //#endregion
196
225
  //#region src/cli/total-of.ts
@@ -314,7 +343,7 @@ function collectPathValue(value, previous = []) {
314
343
  return [...previous, value];
315
344
  }
316
345
  function configureProgramOptions(program, parseMode) {
317
- program.addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).argParser(parseMode).default("chunk")).addOption(new Option("-f, --format <format>", "output format").choices(FORMAT_CHOICES).default("standard")).addOption(new Option("--section <section>", "document section mode").choices(SECTION_CHOICES).default("all")).addOption(new Option("--path-mode <mode>", "path resolution mode").choices(PATH_MODE_CHOICES).default("auto")).option("--latin-language <language>", "hint a language tag for Latin script text").option("--latin-tag <tag>", "hint a BCP 47 tag for Latin script text").option("--latin-locale <locale>", "legacy alias of --latin-language").option("--han-language <language>", "hint a language tag for Han script text").option("--han-tag <tag>", "hint a BCP 47 tag for Han script text").option("--non-words", "collect emoji, symbols, and punctuation (excludes whitespace)").option("--include-whitespace", "include whitespace counts (implies with --non-words; same as --misc)").option("--misc", "collect non-words plus whitespace (alias for --include-whitespace)").option("--total-of <parts>", "override total composition (comma-separated): words,emoji,symbols,punctuation,whitespace", parseTotalOfOption).option("--pretty", "pretty print JSON output", false).option("--debug", "enable debug diagnostics on stderr").option("--verbose", "emit verbose per-file debug diagnostics (requires --debug)").option("--debug-report [path]", "write debug diagnostics to a report file").option("--debug-report-tee", "mirror debug diagnostics to both report file and stderr").option("--debug-tee", "alias of --debug-report-tee").option("--merged", "show merged aggregate output (default)").option("--per-file", "show per-file output plus merged summary").option("--no-progress", "disable batch progress indicator").option("--keep-progress", "keep final batch progress line visible in standard mode").option("--no-recursive", "disable recursive directory traversal").option("--quiet-skips", "hide skip diagnostics (applies when --debug is enabled)").option("--include-ext <exts>", "comma-separated extensions to include during directory scanning", collectExtensionOption, []).option("--exclude-ext <exts>", "comma-separated extensions to exclude during directory scanning", collectExtensionOption, []).option("-p, --path <path>", "read input from file or directory", collectPathValue, []).argument("[text...]", "text to count").showHelpAfterError();
346
+ program.addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).argParser(parseMode).default("chunk")).addOption(new Option("-f, --format <format>", "output format").choices(FORMAT_CHOICES).default("standard")).addOption(new Option("--section <section>", "document section mode").choices(SECTION_CHOICES).default("all")).addOption(new Option("--path-mode <mode>", "path resolution mode: auto (default) expands directories; manual treats --path values as literal files").choices(PATH_MODE_CHOICES).default("auto")).option("--latin-language <language>", "hint a language tag for Latin script text").option("--latin-tag <tag>", "hint a BCP 47 tag for Latin script text").option("--latin-locale <locale>", "legacy alias of --latin-language").option("--han-language <language>", "hint a language tag for Han script text").option("--han-tag <tag>", "hint a BCP 47 tag for Han script text").option("--non-words", "collect emoji, symbols, and punctuation (excludes whitespace)").option("--include-whitespace", "include whitespace counts (implies with --non-words; same as --misc)").option("--misc", "collect non-words plus whitespace (alias for --include-whitespace)").option("--total-of <parts>", "override total composition (comma-separated): words,emoji,symbols,punctuation,whitespace", parseTotalOfOption).option("--pretty", "pretty print JSON output", false).option("--debug", "enable debug diagnostics on stderr").option("--verbose", "emit verbose per-file debug diagnostics (requires --debug)").option("--debug-report [path]", "write debug diagnostics to a report file").option("--debug-report-tee", "mirror debug diagnostics to both report file and stderr").option("--debug-tee", "alias of --debug-report-tee").option("--merged", "show merged aggregate output (default)").option("--per-file", "show per-file output plus merged summary").option("--no-progress", "disable batch progress indicator").option("--keep-progress", "keep final batch progress line visible in standard mode").option("--no-recursive", "disable recursive directory traversal").option("--quiet-skips", "hide skip diagnostics (applies when --debug is enabled)").option("--include-ext <exts>", "comma-separated extensions to include during directory scanning", collectExtensionOption, []).option("--exclude-ext <exts>", "comma-separated extensions to exclude during directory scanning", collectExtensionOption, []).option("--regex <pattern>", "regex filter for directory-scanned paths (applies to --path directories only)").option("-p, --path <path>", "read input from file or directory (directories expand in auto mode by default)", collectPathValue, []).argument("[text...]", "text to count").showHelpAfterError();
318
347
  }
319
348
 
320
349
  //#endregion
@@ -387,8 +416,12 @@ var require_picocolors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
387
416
  }));
388
417
 
389
418
  //#endregion
390
- //#region src/cli/program/version.ts
419
+ //#region src/cli/program/version-embedded.ts
391
420
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
421
+ const EMBEDDED_PACKAGE_VERSION = "0.1.2";
422
+
423
+ //#endregion
424
+ //#region src/cli/program/version.ts
392
425
  function* candidateSearchRoots() {
393
426
  yield dirname(fileURLToPath(import.meta.url));
394
427
  const argvPath = process.argv[1];
@@ -412,13 +445,23 @@ function resolveVersionFromPath(start, maxLevels) {
412
445
  } catch {}
413
446
  return null;
414
447
  }
415
- function resolvePackageVersion() {
416
- const maxLevels = 8;
448
+ function normalizeVersion(value) {
449
+ if (typeof value !== "string") return null;
450
+ const trimmed = value.trim();
451
+ if (!trimmed) return null;
452
+ return trimmed;
453
+ }
454
+ function resolvePackageVersion(options = {}) {
455
+ const embeddedVersion = normalizeVersion(options.embeddedVersion ?? EMBEDDED_PACKAGE_VERSION);
456
+ if (embeddedVersion) return embeddedVersion;
457
+ const maxLevels = options.maxLevels ?? 8;
458
+ const resolveFromPath = options.resolveFromPath ?? resolveVersionFromPath;
459
+ const roots = options.candidateRoots ?? [...candidateSearchRoots()];
417
460
  const seen = /* @__PURE__ */ new Set();
418
- for (const root of candidateSearchRoots()) {
461
+ for (const root of roots) {
419
462
  if (seen.has(root)) continue;
420
463
  seen.add(root);
421
- const version = resolveVersionFromPath(root, maxLevels);
464
+ const version = normalizeVersion(resolveFromPath(root, maxLevels));
422
465
  if (version) return version;
423
466
  }
424
467
  return "0.0.0";
@@ -485,7 +528,7 @@ async function loadBatchInputs(filePaths) {
485
528
 
486
529
  //#endregion
487
530
  //#region src/cli/path/resolve.ts
488
- async function expandDirectory(directoryPath, recursive, filter, skipped, debug, stats) {
531
+ async function expandDirectory(rootPath, directoryPath, recursive, extensionFilter, regexFilter, skipped, recordRegexExcluded, debug, stats) {
489
532
  let entries;
490
533
  try {
491
534
  entries = await readdir(directoryPath, {
@@ -514,7 +557,7 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
514
557
  for (const entry of sortedEntries) {
515
558
  const entryPath = resolve(directoryPath, entry.name);
516
559
  if (entry.isFile()) {
517
- if (!shouldIncludeFromDirectory(entryPath, filter)) {
560
+ if (!shouldIncludeFromDirectory(entryPath, extensionFilter)) {
518
561
  skipped.push({
519
562
  path: entryPath,
520
563
  reason: "extension excluded"
@@ -526,6 +569,19 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
526
569
  stats.filterExcluded += 1;
527
570
  continue;
528
571
  }
572
+ const relativePath = toDirectoryRelativePath(rootPath, entryPath);
573
+ if (!shouldIncludeFromDirectoryRegex(relativePath, regexFilter)) {
574
+ if (recordRegexExcluded(entryPath)) {
575
+ debug.emit("path.resolve.regex.excluded", {
576
+ path: entryPath,
577
+ relativePath,
578
+ pattern: regexFilter.sourcePattern,
579
+ reason: "regex excluded"
580
+ }, { verbosity: "verbose" });
581
+ stats.regexExcluded += 1;
582
+ }
583
+ continue;
584
+ }
529
585
  files.push(entryPath);
530
586
  stats.directoryIncluded += 1;
531
587
  debug.emit("path.resolve.expand.include", {
@@ -535,7 +591,7 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
535
591
  continue;
536
592
  }
537
593
  if (!entry.isDirectory() || !recursive) continue;
538
- appendAll(files, await expandDirectory(entryPath, recursive, filter, skipped, debug, stats));
594
+ appendAll(files, await expandDirectory(rootPath, entryPath, recursive, extensionFilter, regexFilter, skipped, recordRegexExcluded, debug, stats));
539
595
  }
540
596
  debug.emit("path.resolve.expand.complete", {
541
597
  directory: directoryPath,
@@ -545,14 +601,17 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
545
601
  }
546
602
  async function resolveBatchFilePaths(pathInputs, options) {
547
603
  const skipped = [];
604
+ const regexExcludedPaths = /* @__PURE__ */ new Set();
548
605
  const resolvedFiles = /* @__PURE__ */ new Set();
549
606
  const stats = {
550
607
  dedupeAccepted: 0,
551
608
  dedupeDuplicates: 0,
552
609
  filterExcluded: 0,
610
+ regexExcluded: 0,
553
611
  directoryIncluded: 0
554
612
  };
555
613
  const extensionFilter = options.extensionFilter ?? buildDirectoryExtensionFilter(void 0, void 0);
614
+ let regexFilter;
556
615
  const debug = options.debug ?? {
557
616
  enabled: false,
558
617
  verbosity: "compact",
@@ -562,9 +621,11 @@ async function resolveBatchFilePaths(pathInputs, options) {
562
621
  debug.emit("path.resolve.inputs", {
563
622
  inputs: pathInputs.length,
564
623
  pathMode: options.pathMode,
565
- recursive: options.recursive
624
+ recursive: options.recursive,
625
+ hasRegex: Boolean(options.directoryRegexPattern)
566
626
  });
567
627
  const addResolvedFile = (filePath, details) => {
628
+ regexExcludedPaths.delete(filePath);
568
629
  if (resolvedFiles.has(filePath)) {
569
630
  stats.dedupeDuplicates += 1;
570
631
  debug.emit("path.resolve.dedupe.duplicate", {
@@ -582,6 +643,15 @@ async function resolveBatchFilePaths(pathInputs, options) {
582
643
  input: details.input
583
644
  }, { verbosity: "verbose" });
584
645
  };
646
+ const getRegexFilter = () => {
647
+ if (!regexFilter) regexFilter = buildDirectoryRegexFilter(options.directoryRegexPattern);
648
+ return regexFilter;
649
+ };
650
+ const recordRegexExcluded = (filePath) => {
651
+ if (resolvedFiles.has(filePath)) return false;
652
+ regexExcludedPaths.add(filePath);
653
+ return true;
654
+ };
585
655
  for (const rawPath of pathInputs) {
586
656
  const targetPath = resolve(rawPath);
587
657
  debug.emit("path.resolve.input", {
@@ -604,11 +674,13 @@ async function resolveBatchFilePaths(pathInputs, options) {
604
674
  continue;
605
675
  }
606
676
  if (metadata.isDirectory() && options.pathMode === "auto") {
677
+ const effectiveRegexFilter = getRegexFilter();
607
678
  debug.emit("path.resolve.root.expand", {
608
679
  root: targetPath,
609
- recursive: options.recursive
680
+ recursive: options.recursive,
681
+ regex: effectiveRegexFilter.sourcePattern ?? null
610
682
  });
611
- const files = await expandDirectory(targetPath, options.recursive, extensionFilter, skipped, debug, stats);
683
+ const files = await expandDirectory(targetPath, targetPath, options.recursive, extensionFilter, effectiveRegexFilter, skipped, recordRegexExcluded, debug, stats);
612
684
  for (const file of files) addResolvedFile(file, {
613
685
  source: "directory",
614
686
  input: targetPath
@@ -631,9 +703,15 @@ async function resolveBatchFilePaths(pathInputs, options) {
631
703
  input: targetPath
632
704
  });
633
705
  }
706
+ for (const path of regexExcludedPaths) skipped.push({
707
+ path,
708
+ reason: "regex excluded"
709
+ });
634
710
  const files = [...resolvedFiles].sort((left, right) => left.localeCompare(right));
635
711
  debug.emit("path.resolve.filter.summary", {
636
- excluded: stats.filterExcluded,
712
+ excluded: stats.filterExcluded + stats.regexExcluded,
713
+ extensionExcluded: stats.filterExcluded,
714
+ regexExcluded: stats.regexExcluded,
637
715
  included: stats.directoryIncluded
638
716
  });
639
717
  debug.emit("path.resolve.dedupe.summary", {
@@ -2086,6 +2164,7 @@ async function runBatchCount(options) {
2086
2164
  pathMode: options.batchOptions.pathMode,
2087
2165
  recursive: options.batchOptions.recursive,
2088
2166
  extensionFilter: options.extensionFilter,
2167
+ directoryRegexPattern: options.batchOptions.directoryRegexPattern,
2089
2168
  debug: options.debug
2090
2169
  });
2091
2170
  const resolveElapsedMs = Date.now() - resolveStartedAtMs;
@@ -2362,6 +2441,27 @@ function normalizeBatchSummaryBase(summary) {
2362
2441
  function hasPathInput(pathValues) {
2363
2442
  return Array.isArray(pathValues) && pathValues.length > 0;
2364
2443
  }
2444
+ function countLongOptionOccurrences(argv, optionName) {
2445
+ let count = 0;
2446
+ for (let index = 2; index < argv.length; index += 1) {
2447
+ const token = argv[index];
2448
+ if (!token) continue;
2449
+ if (token === "--") break;
2450
+ if (token === optionName) {
2451
+ count += 1;
2452
+ index += 1;
2453
+ continue;
2454
+ }
2455
+ if (token.startsWith(`${optionName}=`)) {
2456
+ count += 1;
2457
+ continue;
2458
+ }
2459
+ }
2460
+ return count;
2461
+ }
2462
+ function validateSingleRegexOptionUsage(argv) {
2463
+ if (countLongOptionOccurrences(argv, "--regex") > 1) throw new Error("`--regex` can only be provided once.");
2464
+ }
2365
2465
  function resolveBatchScope(argv) {
2366
2466
  let scope = "merged";
2367
2467
  for (const token of argv) {
@@ -2413,7 +2513,8 @@ async function executeBatchCount({ argv, options, runtime, resolved, debug, teeE
2413
2513
  scope: resolveBatchScope(argv),
2414
2514
  pathMode: options.pathMode,
2415
2515
  recursive: options.recursive,
2416
- quietSkips: Boolean(options.quietSkips)
2516
+ quietSkips: Boolean(options.quietSkips),
2517
+ directoryRegexPattern: options.regex
2417
2518
  };
2418
2519
  const extensionFilter = buildDirectoryExtensionFilter(options.includeExt, options.excludeExt);
2419
2520
  const debugEnabled = Boolean(options.debug);
@@ -2471,16 +2572,29 @@ async function executeBatchCount({ argv, options, runtime, resolved, debug, teeE
2471
2572
  const spacing = options.pretty ? 2 : 0;
2472
2573
  if (batchOptions.scope === "per-file") {
2473
2574
  const skipped = showSkipDiagnostics ? summary.skipped : void 0;
2575
+ const fileEntries = summary.files.map((file) => {
2576
+ const base = {
2577
+ path: file.path,
2578
+ result: file.result
2579
+ };
2580
+ if (!resolved.totalOfParts || resolved.totalOfParts.length === 0) return base;
2581
+ const fileOverride = totalOfOverridesByResult?.get(file.result) ?? resolveTotalOfOverride(file.result, resolved.totalOfParts);
2582
+ if (!fileOverride) return base;
2583
+ return {
2584
+ ...base,
2585
+ meta: {
2586
+ totalOf: fileOverride.parts,
2587
+ totalOfOverride: fileOverride.total
2588
+ }
2589
+ };
2590
+ });
2474
2591
  const meta = resolved.totalOfParts && resolved.totalOfParts.length > 0 ? {
2475
2592
  totalOf: resolved.totalOfParts,
2476
2593
  aggregateTotalOfOverride: aggregateTotalOfOverride?.total ?? summary.aggregate.total
2477
2594
  } : void 0;
2478
2595
  const payload = {
2479
2596
  scope: "per-file",
2480
- files: summary.files.map((file) => ({
2481
- path: file.path,
2482
- result: file.result
2483
- })),
2597
+ files: fileEntries,
2484
2598
  ...skipped ? { skipped } : {},
2485
2599
  aggregate: summary.aggregate,
2486
2600
  ...meta ? { meta } : {}
@@ -2601,6 +2715,13 @@ async function runCli(argv = process.argv, runtime = {}) {
2601
2715
  program.error(import_picocolors.default.red("`--debug-report-tee` (alias: `--debug-tee`) requires `--debug-report`."));
2602
2716
  return;
2603
2717
  }
2718
+ try {
2719
+ validateSingleRegexOptionUsage(argv);
2720
+ } catch (error) {
2721
+ const message = error instanceof Error ? error.message : String(error);
2722
+ program.error(import_picocolors.default.red(message));
2723
+ return;
2724
+ }
2604
2725
  let debug;
2605
2726
  try {
2606
2727
  debug = createDebugChannel({