@fairfox/polly 0.91.0 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/cli/polly.js +3 -3
  2. package/dist/cli/polly.js.map +3 -3
  3. package/dist/gallery/gallery.css +2 -2
  4. package/dist/gallery/gallery.js +248 -241
  5. package/dist/src/background/index.js +27 -11
  6. package/dist/src/background/index.js.map +2 -2
  7. package/dist/src/background/message-router.js +27 -11
  8. package/dist/src/background/message-router.js.map +2 -2
  9. package/dist/src/client/index.js +56 -56
  10. package/dist/src/client/index.js.map +4 -4
  11. package/dist/src/elysia/index.js +6 -6
  12. package/dist/src/elysia/index.js.map +3 -3
  13. package/dist/src/index.js +27 -11
  14. package/dist/src/index.js.map +2 -2
  15. package/dist/src/mesh.js +2 -2
  16. package/dist/src/mesh.js.map +3 -3
  17. package/dist/src/peer.js +6 -6
  18. package/dist/src/peer.js.map +3 -3
  19. package/dist/src/polly-ui/index.css +2 -2
  20. package/dist/src/polly-ui/markdown.js +1844 -1010
  21. package/dist/src/polly-ui/markdown.js.map +5 -5
  22. package/dist/src/polly-ui/styles.css +2 -2
  23. package/dist/src/shared/adapters/index.js +27 -11
  24. package/dist/src/shared/adapters/index.js.map +2 -2
  25. package/dist/src/shared/lib/context-helpers.js +27 -11
  26. package/dist/src/shared/lib/context-helpers.js.map +2 -2
  27. package/dist/src/shared/lib/message-bus.js +27 -11
  28. package/dist/src/shared/lib/message-bus.js.map +2 -2
  29. package/dist/src/shared/lib/resource.js +17 -1
  30. package/dist/src/shared/lib/resource.js.map +2 -2
  31. package/dist/src/shared/lib/state.js +17 -1
  32. package/dist/src/shared/lib/state.js.map +2 -2
  33. package/dist/src/shared/state/app-state.js +17 -1
  34. package/dist/src/shared/state/app-state.js.map +2 -2
  35. package/dist/tools/bdd/src/cli.js +20 -2
  36. package/dist/tools/bdd/src/cli.js.map +4 -4
  37. package/dist/tools/bdd/src/index.js +20 -2
  38. package/dist/tools/bdd/src/index.js.map +3 -3
  39. package/dist/tools/init/templates/pwa/package.json.template +2 -3
  40. package/dist/tools/mutate/src/args.d.ts +25 -3
  41. package/dist/tools/mutate/src/cli.js +3234 -312
  42. package/dist/tools/mutate/src/cli.js.map +32 -12
  43. package/dist/tools/mutate/src/config.d.ts +13 -2
  44. package/dist/tools/mutate/src/define.d.ts +76 -0
  45. package/dist/tools/mutate/src/engine/ast.d.ts +13 -0
  46. package/dist/tools/mutate/src/engine/baseline.d.ts +20 -0
  47. package/dist/tools/mutate/src/engine/cache.d.ts +32 -0
  48. package/dist/tools/mutate/src/engine/coverage.d.ts +60 -0
  49. package/dist/tools/mutate/src/engine/generate.d.ts +23 -0
  50. package/dist/tools/mutate/src/engine/graph.d.ts +11 -0
  51. package/dist/tools/mutate/src/engine/index.d.ts +16 -0
  52. package/dist/tools/mutate/src/engine/junit.d.ts +38 -0
  53. package/dist/tools/mutate/src/engine/operators.d.ts +14 -0
  54. package/dist/tools/mutate/src/engine/preload.d.ts +10 -0
  55. package/dist/tools/mutate/src/engine/preload.js +180 -0
  56. package/dist/tools/mutate/src/engine/preload.js.map +10 -0
  57. package/dist/tools/mutate/src/engine/probes.d.ts +12 -0
  58. package/dist/tools/mutate/src/engine/report-writer.d.ts +44 -0
  59. package/dist/tools/mutate/src/engine/runner.d.ts +31 -0
  60. package/dist/tools/mutate/src/engine/stray-writes.d.ts +25 -0
  61. package/dist/tools/mutate/src/engine/typecheck.d.ts +37 -0
  62. package/dist/tools/mutate/src/engine/types.d.ts +191 -0
  63. package/dist/tools/mutate/src/engine/verify-callsites.d.ts +9 -0
  64. package/dist/tools/mutate/src/engine/workdir.d.ts +16 -0
  65. package/dist/tools/mutate/src/engine-config.d.ts +7 -0
  66. package/dist/tools/mutate/src/index.d.ts +7 -3
  67. package/dist/tools/mutate/src/index.js +2701 -77
  68. package/dist/tools/mutate/src/index.js.map +26 -7
  69. package/dist/tools/mutate/src/ingest.d.ts +30 -1
  70. package/dist/tools/mutate/src/init.d.ts +2 -1
  71. package/dist/tools/mutate/src/migrate.d.ts +32 -0
  72. package/dist/tools/mutate/src/report.d.ts +3 -0
  73. package/dist/tools/mutate/src/run.d.ts +17 -8
  74. package/dist/tools/mutate/src/verify-matrix.d.ts +12 -15
  75. package/dist/tools/quality/src/cli.js +9 -9
  76. package/dist/tools/quality/src/cli.js.map +4 -4
  77. package/dist/tools/quality/src/index.js +9 -9
  78. package/dist/tools/quality/src/index.js.map +4 -4
  79. package/dist/tools/quality/src/no-tautology-ensures.d.ts +2 -2
  80. package/dist/tools/quality/src/plugins/import-checks.d.ts +1 -1
  81. package/dist/tools/test/src/adapters/index.js +3 -2
  82. package/dist/tools/test/src/adapters/index.js.map +3 -3
  83. package/dist/tools/test/src/coverage-policy/cli.d.ts +2 -2
  84. package/dist/tools/test/src/coverage-policy/cli.js +217 -17
  85. package/dist/tools/test/src/coverage-policy/cli.js.map +7 -5
  86. package/dist/tools/test/src/coverage-policy/index.d.ts +1 -1
  87. package/dist/tools/test/src/coverage-policy/index.js +213 -12
  88. package/dist/tools/test/src/coverage-policy/index.js.map +7 -5
  89. package/dist/tools/test/src/coverage-policy/mutate-targets.d.ts +29 -12
  90. package/dist/tools/test/src/e2e-mesh/index.js +6 -6
  91. package/dist/tools/test/src/e2e-mesh/index.js.map +4 -4
  92. package/dist/tools/test/src/e2e-relay/index.js +230 -226
  93. package/dist/tools/test/src/e2e-relay/index.js.map +7 -7
  94. package/dist/tools/test/src/index.js +3 -2
  95. package/dist/tools/test/src/index.js.map +3 -3
  96. package/dist/tools/test/src/visual/index.js +684 -511
  97. package/dist/tools/test/src/visual/index.js.map +11 -11
  98. package/dist/tools/verify/src/cli.js +2 -2
  99. package/dist/tools/verify/src/cli.js.map +3 -3
  100. package/dist/tools/verify/src/config.d.ts +1 -1
  101. package/dist/tools/verify/src/config.js +68 -3
  102. package/dist/tools/verify/src/config.js.map +4 -4
  103. package/dist/tools/verify/src/primitives/index.d.ts +34 -0
  104. package/package.json +29 -39
  105. package/dist/tools/verify/src/stryker/index.d.ts +0 -32
  106. package/dist/tools/verify/src/stryker/index.js +0 -86
  107. package/dist/tools/verify/src/stryker/index.js.map +0 -10
@@ -170,8 +170,184 @@ init_enforce();
170
170
 
171
171
  // tools/test/src/coverage-policy/mutate-targets.ts
172
172
  import { existsSync as existsSync3, readFileSync } from "node:fs";
173
- import { join as join3, resolve as resolve3 } from "node:path";
173
+ import { dirname, join as join3, resolve as resolve3 } from "node:path";
174
174
  import { Glob as Glob2 } from "bun";
175
+
176
+ // src/shared/lib/guards.ts
177
+ function isRecord(input) {
178
+ return typeof input === "object" && input !== null && !Array.isArray(input);
179
+ }
180
+
181
+ // tools/mutate/src/define.ts
182
+ var MUTATION_CONFIG_FILE = "polly.mutate.ts";
183
+ function defineMutation(config) {
184
+ return config;
185
+ }
186
+ var MUTATOR_NAMES = new Set([
187
+ "ArithmeticOperator",
188
+ "ArrayDeclaration",
189
+ "ArrowFunction",
190
+ "AssignmentOperator",
191
+ "BlockStatement",
192
+ "BooleanLiteral",
193
+ "ConditionalExpression",
194
+ "EqualityOperator",
195
+ "LogicalOperator",
196
+ "MethodExpression",
197
+ "ObjectLiteral",
198
+ "OptionalChaining",
199
+ "Regex",
200
+ "StringLiteral",
201
+ "UnaryOperator",
202
+ "UpdateOperator"
203
+ ]);
204
+ function isMutatorName(value) {
205
+ return typeof value === "string" && MUTATOR_NAMES.has(value);
206
+ }
207
+ function strings(value, where) {
208
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
209
+ throw new Error(`${where} must be an array of strings`);
210
+ }
211
+ return value;
212
+ }
213
+ function optionalNumber(value, where) {
214
+ if (value === undefined)
215
+ return;
216
+ if (typeof value !== "number" || !Number.isFinite(value))
217
+ throw new Error(`${where} must be a number`);
218
+ return value;
219
+ }
220
+ function optionalString(value, where) {
221
+ if (value === undefined)
222
+ return;
223
+ if (typeof value !== "string")
224
+ throw new Error(`${where} must be a string`);
225
+ return value;
226
+ }
227
+ function isEngineMode(v) {
228
+ return v === "fast" || v === "matrix";
229
+ }
230
+ function isCoverageMode(v) {
231
+ return v === "dynamic" || v === "static";
232
+ }
233
+ function parseRun(raw, where) {
234
+ const mode = raw["mode"];
235
+ if (mode !== undefined && !isEngineMode(mode)) {
236
+ throw new Error(`${where}: mode must be "fast" or "matrix"`);
237
+ }
238
+ const concurrency = optionalNumber(raw["concurrency"], `${where}: concurrency`);
239
+ if (concurrency !== undefined && !(Number.isInteger(concurrency) && concurrency > 0)) {
240
+ throw new Error(`${where}: concurrency must be a positive integer`);
241
+ }
242
+ const coverage = raw["coverage"];
243
+ if (coverage !== undefined && !isCoverageMode(coverage)) {
244
+ throw new Error(`${where}: coverage must be "dynamic" or "static"`);
245
+ }
246
+ const common = { targets: strings(raw["targets"], `${where}: targets`) };
247
+ if (isEngineMode(mode))
248
+ common.mode = mode;
249
+ if (isCoverageMode(coverage))
250
+ common.coverage = coverage;
251
+ if (concurrency !== undefined)
252
+ common.concurrency = concurrency;
253
+ const timeout = raw["timeout"];
254
+ if (timeout !== undefined) {
255
+ if (!isRecord(timeout))
256
+ throw new Error(`${where}: timeout must be { factor, extraMs }`);
257
+ const factor = optionalNumber(timeout["factor"], `${where}: timeout.factor`) ?? 2;
258
+ const extraMs = optionalNumber(timeout["extraMs"], `${where}: timeout.extraMs`) ?? 3000;
259
+ common.timeout = { factor, extraMs };
260
+ }
261
+ return common;
262
+ }
263
+ function parseMutants(raw, where) {
264
+ const out = {};
265
+ const mutators = raw["mutators"];
266
+ if (mutators !== undefined) {
267
+ if (!isRecord(mutators))
268
+ throw new Error(`${where}: mutators must be { exclude }`);
269
+ const exclude = mutators["exclude"] === undefined ? [] : mutators["exclude"];
270
+ if (!Array.isArray(exclude) || !exclude.every(isMutatorName)) {
271
+ throw new Error(`${where}: mutators.exclude holds an unknown mutator name`);
272
+ }
273
+ out.mutators = { exclude };
274
+ }
275
+ const ignore = raw["ignoreVerifyCallsites"];
276
+ if (ignore !== undefined) {
277
+ if (typeof ignore !== "boolean")
278
+ throw new Error(`${where}: ignoreVerifyCallsites must be boolean`);
279
+ out.ignoreVerifyCallsites = ignore;
280
+ }
281
+ const typecheck = raw["typecheck"];
282
+ if (typecheck !== undefined) {
283
+ if (typeof typecheck !== "boolean")
284
+ throw new Error(`${where}: typecheck must be boolean`);
285
+ out.typecheck = typecheck;
286
+ }
287
+ return { ...out, ...parseContracts(raw, where) };
288
+ }
289
+ function isContractsMode(v) {
290
+ return v === "off" || v === "ensures" || v === "all";
291
+ }
292
+ function parseContracts(raw, where) {
293
+ const contracts = raw["contracts"];
294
+ if (contracts === undefined)
295
+ return {};
296
+ if (!isContractsMode(contracts)) {
297
+ throw new Error(`${where}: contracts must be "off", "ensures" or "all"`);
298
+ }
299
+ return { contracts };
300
+ }
301
+ function parseOutputs(raw, where) {
302
+ const out = {};
303
+ const thresholds = raw["thresholds"];
304
+ if (thresholds !== undefined) {
305
+ if (!isRecord(thresholds))
306
+ throw new Error(`${where}: thresholds must be { break }`);
307
+ const brk = optionalNumber(thresholds["break"], `${where}: thresholds.break`);
308
+ out.thresholds = brk === undefined ? {} : { break: brk };
309
+ }
310
+ const report = optionalString(raw["report"], `${where}: report`);
311
+ if (report !== undefined)
312
+ out.report = report;
313
+ const decisions = optionalString(raw["decisions"], `${where}: decisions`);
314
+ if (decisions !== undefined)
315
+ out.decisions = decisions;
316
+ return out;
317
+ }
318
+ function parseCommon(raw, where) {
319
+ return { ...parseRun(raw, where), ...parseMutants(raw, where), ...parseOutputs(raw, where) };
320
+ }
321
+ function parseMutationConfig(raw, where) {
322
+ if (!isRecord(raw))
323
+ throw new Error(`${where}: the default export must be a config object`);
324
+ const { targets, ...common } = parseCommon(raw, where);
325
+ const runner = raw["runner"];
326
+ if (isRecord(runner) && runner["kind"] === "command") {
327
+ const argv = strings(runner["argv"], `${where}: runner.argv`);
328
+ if (argv.length === 0)
329
+ throw new Error(`${where}: runner.argv is empty`);
330
+ return { targets, runner: { kind: "command", argv }, ...common };
331
+ }
332
+ if (runner !== undefined && !(isRecord(runner) && runner["kind"] === "bun-test")) {
333
+ throw new Error(`${where}: runner.kind must be "bun-test" or "command"`);
334
+ }
335
+ const tests = raw["tests"];
336
+ if (!isRecord(tests))
337
+ throw new Error(`${where}: tests.include is required`);
338
+ const include = strings(tests["include"], `${where}: tests.include`);
339
+ if (include.length === 0)
340
+ throw new Error(`${where}: tests.include is empty`);
341
+ const preload = tests["preload"] === undefined ? undefined : strings(tests["preload"], `${where}: tests.preload`);
342
+ return { targets, tests: preload ? { include, preload } : { include }, ...common };
343
+ }
344
+ async function loadMutationConfig(path) {
345
+ const mod = await import(path);
346
+ const raw = isRecord(mod) ? mod["default"] : undefined;
347
+ return parseMutationConfig(raw, path);
348
+ }
349
+
350
+ // tools/test/src/coverage-policy/mutate-targets.ts
175
351
  async function findStrykerConfigs(root) {
176
352
  const found = [];
177
353
  const single = join3(root, "stryker.conf.json");
@@ -183,12 +359,37 @@ async function findStrykerConfigs(root) {
183
359
  }
184
360
  return found.sort();
185
361
  }
362
+ async function findMutateConfigs(root) {
363
+ const out = [];
364
+ const polly = join3(root, MUTATION_CONFIG_FILE);
365
+ if (existsSync3(polly)) {
366
+ const config = await loadMutationConfig(polly);
367
+ out.push({
368
+ path: polly,
369
+ format: "polly",
370
+ targets: { field: "targets", patterns: config.targets },
371
+ tests: { field: "tests.include", patterns: config.tests?.include ?? [] }
372
+ });
373
+ }
374
+ for (const path of await findStrykerConfigs(root)) {
375
+ const config = JSON.parse(readFileSync(path, "utf8"));
376
+ out.push({
377
+ path,
378
+ format: "stryker",
379
+ targets: { field: "mutate", patterns: config.mutate ?? [] },
380
+ tests: { field: "testFiles", patterns: config.testFiles ?? config.bun?.testFiles ?? [] }
381
+ });
382
+ }
383
+ return out;
384
+ }
186
385
  function isGlob(pattern) {
187
386
  return pattern.includes("*") || pattern.includes("?") || pattern.includes("[");
188
387
  }
189
- async function resolvesToFile(pattern, cwd) {
190
- if (pattern.startsWith("!"))
388
+ var LINE_RANGE = /:\d+(?::\d+)?-\d+(?::\d+)?$/;
389
+ async function resolvesToFile(entry, cwd) {
390
+ if (entry.startsWith("!"))
191
391
  return true;
392
+ const pattern = entry.replace(LINE_RANGE, "");
192
393
  if (!isGlob(pattern))
193
394
  return existsSync3(resolve3(cwd, pattern));
194
395
  const glob = new Glob2(pattern);
@@ -198,7 +399,7 @@ async function resolvesToFile(pattern, cwd) {
198
399
  }
199
400
  async function checkField(configPath, field, patterns, cwd) {
200
401
  const issues = [];
201
- for (const pattern of patterns ?? []) {
402
+ for (const pattern of patterns) {
202
403
  if (!await resolvesToFile(pattern, cwd)) {
203
404
  issues.push({ config: configPath, field, pattern });
204
405
  }
@@ -206,19 +407,19 @@ async function checkField(configPath, field, patterns, cwd) {
206
407
  return issues;
207
408
  }
208
409
  async function validateMutateTargets(root) {
209
- const configs = await findStrykerConfigs(root);
410
+ const configs = await findMutateConfigs(root);
210
411
  const issues = [];
211
- for (const configPath of configs) {
212
- const config = JSON.parse(readFileSync(configPath, "utf8"));
213
- const testFiles = config.testFiles ?? config.bun?.testFiles;
214
- issues.push(...await checkField(configPath, "mutate", config.mutate, root));
215
- issues.push(...await checkField(configPath, "testFiles", testFiles, root));
412
+ for (const c of configs) {
413
+ const cwd = c.format === "polly" ? dirname(c.path) : root;
414
+ issues.push(...await checkField(c.path, c.targets.field, c.targets.patterns, cwd));
415
+ issues.push(...await checkField(c.path, c.tests.field, c.tests.patterns, cwd));
216
416
  }
217
- return { configs, issues };
417
+ return { configs: configs.map((c) => c.path), issues };
218
418
  }
219
419
  export {
220
420
  enforceCoverage,
221
421
  evaluateCoverage,
422
+ findMutateConfigs,
222
423
  findStrykerConfigs,
223
424
  hasFailure,
224
425
  loadCoverageConfig,
@@ -227,4 +428,4 @@ export {
227
428
  validateMutateTargets
228
429
  };
229
430
 
230
- //# debugId=5C2B3898BBAEBE0B64756E2164756E21
431
+ //# debugId=3F48DAA595C6EBD164756E2164756E21
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../tools/test/src/coverage-policy/enforce.ts", "../tools/test/src/coverage-policy/discover.ts", "../tools/test/src/coverage-policy/index.ts", "../tools/test/src/coverage-policy/mutate-targets.ts"],
3
+ "sources": ["../tools/test/src/coverage-policy/enforce.ts", "../tools/test/src/coverage-policy/discover.ts", "../tools/test/src/coverage-policy/index.ts", "../tools/test/src/coverage-policy/mutate-targets.ts", "../src/shared/lib/guards.ts", "../tools/mutate/src/define.ts"],
4
4
  "sourcesContent": [
5
5
  "/**\n * @fairfox/polly/test/coverage — the coverage-policy engine.\n *\n * Parses a `bun test --coverage` table and applies a {@link CoverageConfig}.\n * It fails on four conditions, not just low numbers: a non-exempt file below\n * the floor, an exempt key whose source no longer exists, an exemption whose\n * `claimedBy` test no longer exists, and an exempt file that has climbed back\n * over the floor (promote it). It also reports orphans — source files no unit\n * test imports, the blind spot a coverage table can't show.\n *\n * Everything here is parameterised by the project root, so the same engine\n * backs Polly's own gate and the consumer-facing `polly coverage` command.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { Glob } from \"bun\";\nimport type { CoverageConfig } from \"./types\";\n\nexport interface CoverageRow {\n /** Project-relative, e.g. `src/shared/lib/state.ts`. */\n file: string;\n funcs: number;\n lines: number;\n}\n\nexport interface Violation {\n file: string;\n metric: \"lines\" | \"funcs\";\n observed: number;\n required: number;\n}\n\nexport interface CoverageFindings {\n rowCount: number;\n violations: Violation[];\n staleExempts: string[];\n missingExemptFiles: string[];\n missingClaimedBy: Array<{ file: string; claimedBy: string }>;\n orphans: string[];\n /** True when a floor was configured; false means report-only. */\n enforced: boolean;\n}\n\nconst DEFAULT_SRC = \"src\";\n\n/** `../src/foo.ts` (run from a subdir) → `src/foo.ts` (project-relative). */\nfunction normalizePath(raw: string): string {\n return raw.replace(/^(?:\\.\\.\\/)+/, \"\");\n}\n\n/**\n * Parse the `bun test --coverage` table. Only rows under `srcDir` are\n * policy-bearing; the `All files` summary and test-infra rows are skipped.\n * Column order is `File | % Funcs | % Lines | Uncovered`.\n */\nexport function parseCoverageTable(text: string, srcDir: string): CoverageRow[] {\n const prefix = `${srcDir}/`;\n const rows: CoverageRow[] = [];\n for (const line of text.split(\"\\n\")) {\n if (!line.includes(\"|\")) continue;\n if (line.includes(\"All files\") || line.includes(\"% Funcs\")) continue;\n if (line.trim().startsWith(\"---\")) continue;\n\n const cells = line.split(\"|\").map((c) => c.trim());\n if (cells.length < 3) continue;\n\n const file = normalizePath(cells[0] ?? \"\");\n const funcs = Number(cells[1]);\n const lines = Number(cells[2]);\n if (!file.startsWith(prefix) || Number.isNaN(funcs) || Number.isNaN(lines)) continue;\n\n rows.push({ file, funcs, lines });\n }\n return rows;\n}\n\n/** Run `bun test --coverage` in the configured cwd and return combined output. */\nexport async function runCoverage(root: string, testCwd: string): Promise<string> {\n const proc = Bun.spawn([\"bun\", \"test\", \"--coverage\"], {\n cwd: join(root, testCwd),\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n const [out, err] = await Promise.all([\n new Response(proc.stdout).text(),\n new Response(proc.stderr).text(),\n ]);\n await proc.exited;\n if (proc.exitCode !== 0) {\n throw new Error(`bun test --coverage exited ${proc.exitCode}\\n${err}`);\n }\n return `${out}\\n${err}`;\n}\n\nfunction evaluateRows(\n rows: CoverageRow[],\n config: CoverageConfig\n): { violations: Violation[]; staleExempts: string[] } {\n const t = config.defaultThreshold;\n const exempt = config.exempt ?? {};\n const violations: Violation[] = [];\n const staleExempts: string[] = [];\n if (!t) return { violations, staleExempts };\n\n for (const row of rows) {\n if (exempt[row.file]) {\n if (row.lines >= t.lines && row.funcs >= t.funcs) staleExempts.push(row.file);\n continue;\n }\n if (row.lines < t.lines) {\n violations.push({ file: row.file, metric: \"lines\", observed: row.lines, required: t.lines });\n }\n if (row.funcs < t.funcs) {\n violations.push({ file: row.file, metric: \"funcs\", observed: row.funcs, required: t.funcs });\n }\n }\n return { violations, staleExempts };\n}\n\nfunction validateExemptions(\n root: string,\n config: CoverageConfig\n): { missingExemptFiles: string[]; missingClaimedBy: Array<{ file: string; claimedBy: string }> } {\n const missingExemptFiles: string[] = [];\n const missingClaimedBy: Array<{ file: string; claimedBy: string }> = [];\n for (const [file, entry] of Object.entries(config.exempt ?? {})) {\n if (!existsSync(resolve(root, file))) missingExemptFiles.push(file);\n const claimedBy = entry.claimedBy.trim();\n if (!claimedBy.startsWith(\"n/a\") && !existsSync(resolve(root, claimedBy))) {\n missingClaimedBy.push({ file, claimedBy });\n }\n }\n return { missingExemptFiles, missingClaimedBy };\n}\n\n/** Source files no row covers and no exemption names — the coverage blind spot. */\nasync function findOrphans(\n root: string,\n srcDir: string,\n covered: Set<string>,\n config: CoverageConfig\n): Promise<string[]> {\n const exempt = config.exempt ?? {};\n const orphans: string[] = [];\n const glob = new Glob(`${srcDir}/**/*.{ts,tsx}`);\n for await (const file of glob.scan({ cwd: root, onlyFiles: true })) {\n if (file.endsWith(\".d.ts\") || /\\.test\\.tsx?$/.test(file) || file.includes(\"/__tests__/\")) {\n continue;\n }\n if (covered.has(file) || exempt[file]) continue;\n orphans.push(file);\n }\n return orphans.sort();\n}\n\n/** Apply the policy to a parsed table. Pure — no spawning. */\nexport async function evaluateCoverage(\n root: string,\n rows: CoverageRow[],\n config: CoverageConfig\n): Promise<CoverageFindings> {\n const srcDir = config.srcDir ?? DEFAULT_SRC;\n const { violations, staleExempts } = evaluateRows(rows, config);\n const { missingExemptFiles, missingClaimedBy } = validateExemptions(root, config);\n const orphans = await findOrphans(root, srcDir, new Set(rows.map((r) => r.file)), config);\n return {\n rowCount: rows.length,\n violations,\n staleExempts,\n missingExemptFiles,\n missingClaimedBy,\n orphans,\n enforced: config.defaultThreshold !== undefined,\n };\n}\n\n/** Run the suite under coverage and apply the policy. */\nexport async function enforceCoverage(\n root: string,\n config: CoverageConfig,\n coverageText?: string\n): Promise<CoverageFindings> {\n const srcDir = config.srcDir ?? DEFAULT_SRC;\n const text = coverageText ?? (await runCoverage(root, config.testCwd ?? \".\"));\n const rows = parseCoverageTable(text, srcDir);\n return evaluateCoverage(root, rows, config);\n}\n\n/** True when the findings represent a policy failure (orphans are advisory\n * unless `strictOrphans`). */\nexport function hasFailure(findings: CoverageFindings, strictOrphans: boolean): boolean {\n return (\n findings.violations.length > 0 ||\n findings.staleExempts.length > 0 ||\n findings.missingExemptFiles.length > 0 ||\n findings.missingClaimedBy.length > 0 ||\n (strictOrphans && findings.orphans.length > 0)\n );\n}\n",
6
6
  "/**\n * @fairfox/polly/test/coverage — zero-config loading.\n *\n * `polly coverage` runs without any config: it looks for a `coverage.config.ts`\n * (or `.js`) at the project root, and if there is none it returns an empty\n * config, which the engine treats as report-only — it prints the numbers and\n * the orphan count but never fails the build. A consumer opts into enforcement\n * by adding a `defaultThreshold`, and into legible tiering by adding `exempt`\n * entries. This mirrors how `polly test --tier` discovers tiers by convention.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport type { CoverageConfig } from \"./types\";\n\nexport interface LoadedConfig {\n config: CoverageConfig;\n /** Absolute path the config was loaded from, or null for the zero-config default. */\n source: string | null;\n}\n\nfunction isCoverageConfig(value: unknown): value is CoverageConfig {\n return typeof value === \"object\" && value !== null;\n}\n\nasync function importConfig(path: string): Promise<CoverageConfig> {\n const mod: Record<string, unknown> = await import(path);\n const candidate = mod[\"config\"] ?? mod[\"default\"];\n if (!isCoverageConfig(candidate)) {\n throw new Error(`${path} must export \\`config\\` (a CoverageConfig object)`);\n }\n return candidate;\n}\n\n/**\n * Resolve the coverage config. With `explicitPath` (Polly's own front-end\n * passes scripts/coverage.config.ts) that file must exist. Otherwise look for\n * coverage.config.{ts,js} at the root; absent → the zero-config report-only\n * default.\n */\nexport async function loadCoverageConfig(\n root: string,\n explicitPath?: string\n): Promise<LoadedConfig> {\n if (explicitPath) {\n const abs = isAbsolute(explicitPath) ? explicitPath : resolve(root, explicitPath);\n if (!existsSync(abs)) throw new Error(`coverage config not found: ${abs}`);\n return { config: await importConfig(abs), source: abs };\n }\n\n for (const name of [\"coverage.config.ts\", \"coverage.config.js\"]) {\n const abs = join(root, name);\n if (existsSync(abs)) return { config: await importConfig(abs), source: abs };\n }\n return { config: {}, source: null };\n}\n",
7
- "/**\n * @fairfox/polly/test/coverage — programmatic API behind `polly coverage`.\n *\n * Use the CLI (`polly coverage`) for the common case; import these to script\n * the policy yourself or to add a coverage tier to a custom runner.\n */\n\nexport { type LoadedConfig, loadCoverageConfig } from \"./discover\";\nexport {\n type CoverageFindings,\n type CoverageRow,\n enforceCoverage,\n evaluateCoverage,\n hasFailure,\n parseCoverageTable,\n runCoverage,\n type Violation,\n} from \"./enforce\";\nexport {\n findStrykerConfigs,\n type MutateTargetIssue,\n type MutateTargetReport,\n validateMutateTargets,\n} from \"./mutate-targets\";\nexport type { CoverageConfig, ExemptEntry, FileThreshold } from \"./types\";\n",
8
- "/**\n * @fairfox/polly/test/coverage — Stryker mutate-/test-target validation.\n *\n * A Stryker config's `mutate` and `testFiles` lists are hand-curated. A path\n * that is renamed, or a glob whose directory moves, silently resolves to\n * nothing — Stryker mutates fewer files (or none) and the only signal is a\n * long mutation run whose score quietly drops. This asserts, in milliseconds,\n * that every entry still resolves to at least one file.\n *\n * Discovers configs two ways, covering both layouts in the wild: a single\n * `stryker.conf.json` at the root, and a `stryker/` directory of per-package\n * shards.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { Glob } from \"bun\";\n\ninterface StrykerConfig {\n mutate?: string[];\n testFiles?: string[];\n bun?: { testFiles?: string[] };\n}\n\nexport interface MutateTargetIssue {\n config: string;\n field: \"mutate\" | \"testFiles\";\n pattern: string;\n}\n\nexport interface MutateTargetReport {\n configs: string[];\n issues: MutateTargetIssue[];\n}\n\n/** Locate every Stryker config under the project root. */\nexport async function findStrykerConfigs(root: string): Promise<string[]> {\n const found: string[] = [];\n const single = join(root, \"stryker.conf.json\");\n if (existsSync(single)) found.push(single);\n\n const glob = new Glob(\"stryker/*.{json,conf.json}\");\n for await (const rel of glob.scan({ cwd: root, onlyFiles: true })) {\n found.push(join(root, rel));\n }\n return found.sort();\n}\n\nfunction isGlob(pattern: string): boolean {\n return pattern.includes(\"*\") || pattern.includes(\"?\") || pattern.includes(\"[\");\n}\n\nasync function resolvesToFile(pattern: string, cwd: string): Promise<boolean> {\n if (pattern.startsWith(\"!\")) return true; // negations are filters, not targets\n if (!isGlob(pattern)) return existsSync(resolve(cwd, pattern));\n const glob = new Glob(pattern);\n for await (const _ of glob.scan({ cwd, onlyFiles: true })) return true;\n return false;\n}\n\nasync function checkField(\n configPath: string,\n field: \"mutate\" | \"testFiles\",\n patterns: string[] | undefined,\n cwd: string\n): Promise<MutateTargetIssue[]> {\n const issues: MutateTargetIssue[] = [];\n for (const pattern of patterns ?? []) {\n if (!(await resolvesToFile(pattern, cwd))) {\n issues.push({ config: configPath, field, pattern });\n }\n }\n return issues;\n}\n\n/**\n * Validate every Stryker config under `root`. Paths in a Stryker config are\n * relative to that config's directory; for the monorepo-root configs that is\n * the root, so we resolve globs against `root`.\n */\nexport async function validateMutateTargets(root: string): Promise<MutateTargetReport> {\n const configs = await findStrykerConfigs(root);\n const issues: MutateTargetIssue[] = [];\n for (const configPath of configs) {\n const config = JSON.parse(readFileSync(configPath, \"utf8\")) as unknown as StrykerConfig;\n const testFiles = config.testFiles ?? config.bun?.testFiles;\n issues.push(...(await checkField(configPath, \"mutate\", config.mutate, root)));\n issues.push(...(await checkField(configPath, \"testFiles\", testFiles, root)));\n }\n return { configs, issues };\n}\n"
7
+ "/**\n * @fairfox/polly/test/coverage — programmatic API behind `polly coverage`.\n *\n * Use the CLI (`polly coverage`) for the common case; import these to script\n * the policy yourself or to add a coverage tier to a custom runner.\n */\n\nexport { type LoadedConfig, loadCoverageConfig } from \"./discover\";\nexport {\n type CoverageFindings,\n type CoverageRow,\n enforceCoverage,\n evaluateCoverage,\n hasFailure,\n parseCoverageTable,\n runCoverage,\n type Violation,\n} from \"./enforce\";\nexport {\n findMutateConfigs,\n findStrykerConfigs,\n type MutateConfigEntry,\n type MutateTargetIssue,\n type MutateTargetReport,\n validateMutateTargets,\n} from \"./mutate-targets\";\nexport type { CoverageConfig, ExemptEntry, FileThreshold } from \"./types\";\n",
8
+ "/**\n * @fairfox/polly/test/coverage — mutation target / test-glob validation.\n *\n * A mutation config's target and test lists are hand-curated. A path that is\n * renamed, or a glob whose directory moves, silently resolves to nothing —\n * fewer files (or none) are mutated and the only signal is a long mutation\n * run whose score quietly drops. This asserts, in milliseconds, that every\n * entry still resolves to at least one file.\n *\n * Reads both config formats: `polly.mutate.ts` at the root, and Stryker JSON\n * (deprecated) — a single `stryker.conf.json` at the root or a `stryker/`\n * directory of per-package shards.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { Glob } from \"bun\";\nimport { loadMutationConfig, MUTATION_CONFIG_FILE } from \"../../../mutate/src/define.ts\";\n\ninterface StrykerConfig {\n mutate?: string[];\n testFiles?: string[];\n bun?: { testFiles?: string[] };\n}\n\nexport interface MutateTargetIssue {\n config: string;\n /** `mutate`/`testFiles` for a Stryker config, `targets`/`tests.include` for polly.mutate.ts. */\n field: \"mutate\" | \"testFiles\" | \"targets\" | \"tests.include\";\n pattern: string;\n}\n\n/** One mutation config, reduced to the paths it names. */\nexport interface MutateConfigEntry {\n path: string;\n format: \"polly\" | \"stryker\";\n targets: { field: MutateTargetIssue[\"field\"]; patterns: string[] };\n tests: { field: MutateTargetIssue[\"field\"]; patterns: string[] };\n}\n\nexport interface MutateTargetReport {\n configs: string[];\n issues: MutateTargetIssue[];\n}\n\n/** Locate every Stryker config under the project root. */\nexport async function findStrykerConfigs(root: string): Promise<string[]> {\n const found: string[] = [];\n const single = join(root, \"stryker.conf.json\");\n if (existsSync(single)) found.push(single);\n\n const glob = new Glob(\"stryker/*.{json,conf.json}\");\n for await (const rel of glob.scan({ cwd: root, onlyFiles: true })) {\n found.push(join(root, rel));\n }\n return found.sort();\n}\n\n/** Every mutation config under `root`, both formats, with the paths each names. */\nexport async function findMutateConfigs(root: string): Promise<MutateConfigEntry[]> {\n const out: MutateConfigEntry[] = [];\n const polly = join(root, MUTATION_CONFIG_FILE);\n if (existsSync(polly)) {\n const config = await loadMutationConfig(polly);\n out.push({\n path: polly,\n format: \"polly\",\n targets: { field: \"targets\", patterns: config.targets },\n tests: { field: \"tests.include\", patterns: config.tests?.include ?? [] },\n });\n }\n for (const path of await findStrykerConfigs(root)) {\n const config = JSON.parse(readFileSync(path, \"utf8\")) as unknown as StrykerConfig;\n out.push({\n path,\n format: \"stryker\",\n targets: { field: \"mutate\", patterns: config.mutate ?? [] },\n tests: { field: \"testFiles\", patterns: config.testFiles ?? config.bun?.testFiles ?? [] },\n });\n }\n return out;\n}\n\nfunction isGlob(pattern: string): boolean {\n return pattern.includes(\"*\") || pattern.includes(\"?\") || pattern.includes(\"[\");\n}\n\n/** `file.ts:10-20` (or `:10:1-20:5`) names `file.ts`. */\nconst LINE_RANGE = /:\\d+(?::\\d+)?-\\d+(?::\\d+)?$/;\n\nasync function resolvesToFile(entry: string, cwd: string): Promise<boolean> {\n if (entry.startsWith(\"!\")) return true; // negations are filters, not targets\n const pattern = entry.replace(LINE_RANGE, \"\");\n if (!isGlob(pattern)) return existsSync(resolve(cwd, pattern));\n const glob = new Glob(pattern);\n for await (const _ of glob.scan({ cwd, onlyFiles: true })) return true;\n return false;\n}\n\nasync function checkField(\n configPath: string,\n field: MutateTargetIssue[\"field\"],\n patterns: string[],\n cwd: string\n): Promise<MutateTargetIssue[]> {\n const issues: MutateTargetIssue[] = [];\n for (const pattern of patterns) {\n if (!(await resolvesToFile(pattern, cwd))) {\n issues.push({ config: configPath, field, pattern });\n }\n }\n return issues;\n}\n\n/**\n * Validate every mutation config under `root`. polly.mutate.ts paths are\n * relative to its own directory (the root). Paths in a Stryker config are\n * relative to that config's directory; for the monorepo-root configs that is\n * the root, so Stryker globs resolve against `root`.\n */\nexport async function validateMutateTargets(root: string): Promise<MutateTargetReport> {\n const configs = await findMutateConfigs(root);\n const issues: MutateTargetIssue[] = [];\n for (const c of configs) {\n const cwd = c.format === \"polly\" ? dirname(c.path) : root;\n issues.push(...(await checkField(c.path, c.targets.field, c.targets.patterns, cwd)));\n issues.push(...(await checkField(c.path, c.tests.field, c.tests.patterns, cwd)));\n }\n return { configs: configs.map((c) => c.path), issues };\n}\n",
9
+ "/**\n * Type guards for walking `unknown` values safely.\n *\n * Polly touches a lot of shapes it doesn't own: parsed JSON bodies\n * from the signalling server, IndexedDB records, `postMessage`\n * payloads, storage adapter returns. TypeScript sees every one of\n * those as `unknown`, and the ergonomic-but-unsafe fix — a bare `as`\n * cast — hides every shape mismatch until runtime. The helpers below\n * are the thinnest possible layer that turns a runtime shape check\n * into a compile-time narrowing, so the code that follows can read\n * the value without a cast.\n *\n * Each guard checks own-properties only (`Object.hasOwn`), not the\n * prototype chain. Walking into a prototype would make a simple\n * `hasKeyInObject(x, \"toString\")` pass for any object, which is\n * never what a caller looking for a specific data key intends.\n */\n\n/** Narrow `input` to an object carrying its own `key`. Returns true\n * when the input is a non-null object with an own property under\n * the given key. The value under the key is left as `unknown`;\n * callers narrow further with `Array.isArray`, `typeof`, or another\n * guard. */\nexport function hasKeyInObject<K extends string>(\n input: unknown,\n key: K\n): input is Record<K, unknown> {\n return typeof input === \"object\" && input !== null && Object.hasOwn(input, key);\n}\n\n/** Narrow `input` to a plain object (non-null, non-array). Useful as\n * a prelude to reads off a record whose shape is known at the\n * call site but typed as `unknown`. */\nexport function isRecord(input: unknown): input is Record<string, unknown> {\n return typeof input === \"object\" && input !== null && !Array.isArray(input);\n}\n\n/**\n * Compile-time exhaustiveness check for discriminated unions.\n *\n * A string-literal union is decorative without one: add a third\n * variant and nothing flags the `switch` statements that only handled\n * the first two — the new case silently falls through whatever\n * `default` arm exists. Placing `assertNever` in that `default` arm\n * turns the gap into a type error, because `value` is only assignable\n * to `never` when every other case has already been handled. The\n * moment a variant is added and a switch forgets it, `value` is no\n * longer `never` and the call fails to type-check.\n *\n * ```ts\n * switch (event.kind) {\n * case \"a\": return handleA(event);\n * case \"b\": return handleB(event);\n * default: return assertNever(event);\n * }\n * ```\n *\n * The `throw` is the runtime backstop for the case TypeScript cannot\n * see — a value arriving from outside the type system (a parsed JSON\n * body, a `postMessage` payload) that violates the declared union.\n */\nexport function assertNever(value: never): never {\n throw new Error(`assertNever: unexpected value ${JSON.stringify(value)}`);\n}\n",
10
+ "/**\n * `polly.mutate.ts` — the typed config for `polly mutate`:\n *\n * import { defineMutation } from \"@fairfox/polly/mutate\";\n * export default defineMutation({\n * targets: [\"src/**\\/*.ts\", \"!src/**\\/*.test.ts\"],\n * tests: { include: [\"tests/**\\/*.test.ts\"] },\n * });\n *\n * Paths are relative to the config file's directory, which is also where the\n * tests run. `defineMutation` only checks the shape at the type level; the\n * CLI loads the file with a plain dynamic import and validates the default\n * export with `parseMutationConfig`, so a hand-written `.js` config with a\n * typo fails with a message, not a crash deep in the engine.\n */\nimport { isRecord } from \"../../../src/shared/lib/guards\";\nimport type { ContractsMode, CoverageMode, EngineMode, MutatorName } from \"./engine/types.ts\";\n\nexport const MUTATION_CONFIG_FILE = \"polly.mutate.ts\";\n\nexport interface MutationTests {\n /** Test-file globs. The engine runs only the files whose imports reach a target. */\n include: string[];\n /** Project preloads, passed to `bun test` before the injection preload. */\n preload?: string[];\n}\n\nexport interface CommandRunner {\n kind: \"command\";\n /**\n * The command, run once per mutant in the config dir; non-zero exit kills\n * the mutant. Every Bun process it starts gets the injection preload via\n * BUN_OPTIONS. No test selection, fast mode only.\n */\n argv: string[];\n}\n\ninterface MutationCommon {\n /** Files to mutate: globs, `!` exclusions, and `file.ts:10-20` line ranges. */\n targets: string[];\n /** fast stops at the first failing test; matrix records every killer. Default fast. */\n mode?: EngineMode;\n /**\n * dynamic: one probed pass over every test file finds the tests that run\n * each mutant; a mutant no test runs is NoCoverage. static: the test files\n * that import the target. Default dynamic; the command runner is always static.\n */\n coverage?: CoverageMode;\n /** Parallel test processes. Default: cores − 1. */\n concurrency?: number;\n /** Per-mutant timeout = baseline × factor + extraMs. Default 2 / 3000. */\n timeout?: { factor: number; extraMs: number };\n mutators?: { exclude?: MutatorName[] };\n /**\n * Report mutants the project's TypeScript config rejects as CompileError,\n * never run (TypeScript 7 through the `typescript-7` dependency). Default true.\n */\n typecheck?: boolean;\n /** Report mutants inside requires/ensures/… as Ignored (polly#143). Default true. */\n ignoreVerifyCallsites?: boolean;\n /**\n * POLLY_CONTRACTS for every test process: a false requires/ensures throws,\n * so each contract kills the mutants that falsify it. off keeps them\n * no-ops; ensures enforces postconditions only. Default all.\n */\n contracts?: ContractsMode;\n /** `polly mutate run` exits 1 when the score is below `break`. */\n thresholds?: { break?: number };\n /** Report path. Default reports/mutation/mutation.json. */\n report?: string;\n /** Verdict log. Default .polly/test-debt/decisions.jsonl. */\n decisions?: string;\n}\n\nexport type MutationConfig = MutationCommon &\n (\n | { runner?: { kind: \"bun-test\" }; tests: MutationTests }\n | { runner: CommandRunner; tests?: undefined }\n );\n\nexport function defineMutation(config: MutationConfig): MutationConfig {\n return config;\n}\n\nconst MUTATOR_NAMES: ReadonlySet<string> = new Set<MutatorName>([\n \"ArithmeticOperator\",\n \"ArrayDeclaration\",\n \"ArrowFunction\",\n \"AssignmentOperator\",\n \"BlockStatement\",\n \"BooleanLiteral\",\n \"ConditionalExpression\",\n \"EqualityOperator\",\n \"LogicalOperator\",\n \"MethodExpression\",\n \"ObjectLiteral\",\n \"OptionalChaining\",\n \"Regex\",\n \"StringLiteral\",\n \"UnaryOperator\",\n \"UpdateOperator\",\n]);\n\nfunction isMutatorName(value: unknown): value is MutatorName {\n return typeof value === \"string\" && MUTATOR_NAMES.has(value);\n}\n\nfunction strings(value: unknown, where: string): string[] {\n if (!Array.isArray(value) || !value.every((v) => typeof v === \"string\")) {\n throw new Error(`${where} must be an array of strings`);\n }\n return value;\n}\n\nfunction optionalNumber(value: unknown, where: string): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"number\" || !Number.isFinite(value))\n throw new Error(`${where} must be a number`);\n return value;\n}\n\nfunction optionalString(value: unknown, where: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\") throw new Error(`${where} must be a string`);\n return value;\n}\n\nfunction isEngineMode(v: unknown): v is EngineMode {\n return v === \"fast\" || v === \"matrix\";\n}\n\nfunction isCoverageMode(v: unknown): v is CoverageMode {\n return v === \"dynamic\" || v === \"static\";\n}\n\nfunction parseRun(raw: Record<string, unknown>, where: string): MutationCommon {\n const mode = raw[\"mode\"];\n if (mode !== undefined && !isEngineMode(mode)) {\n throw new Error(`${where}: mode must be \"fast\" or \"matrix\"`);\n }\n const concurrency = optionalNumber(raw[\"concurrency\"], `${where}: concurrency`);\n if (concurrency !== undefined && !(Number.isInteger(concurrency) && concurrency > 0)) {\n throw new Error(`${where}: concurrency must be a positive integer`);\n }\n const coverage = raw[\"coverage\"];\n if (coverage !== undefined && !isCoverageMode(coverage)) {\n throw new Error(`${where}: coverage must be \"dynamic\" or \"static\"`);\n }\n const common: MutationCommon = { targets: strings(raw[\"targets\"], `${where}: targets`) };\n if (isEngineMode(mode)) common.mode = mode;\n if (isCoverageMode(coverage)) common.coverage = coverage;\n if (concurrency !== undefined) common.concurrency = concurrency;\n const timeout = raw[\"timeout\"];\n if (timeout !== undefined) {\n if (!isRecord(timeout)) throw new Error(`${where}: timeout must be { factor, extraMs }`);\n const factor = optionalNumber(timeout[\"factor\"], `${where}: timeout.factor`) ?? 2;\n const extraMs = optionalNumber(timeout[\"extraMs\"], `${where}: timeout.extraMs`) ?? 3000;\n common.timeout = { factor, extraMs };\n }\n return common;\n}\n\nfunction parseMutants(raw: Record<string, unknown>, where: string): Partial<MutationCommon> {\n const out: Partial<MutationCommon> = {};\n const mutators = raw[\"mutators\"];\n if (mutators !== undefined) {\n if (!isRecord(mutators)) throw new Error(`${where}: mutators must be { exclude }`);\n const exclude = mutators[\"exclude\"] === undefined ? [] : mutators[\"exclude\"];\n if (!Array.isArray(exclude) || !exclude.every(isMutatorName)) {\n throw new Error(`${where}: mutators.exclude holds an unknown mutator name`);\n }\n out.mutators = { exclude };\n }\n const ignore = raw[\"ignoreVerifyCallsites\"];\n if (ignore !== undefined) {\n if (typeof ignore !== \"boolean\")\n throw new Error(`${where}: ignoreVerifyCallsites must be boolean`);\n out.ignoreVerifyCallsites = ignore;\n }\n const typecheck = raw[\"typecheck\"];\n if (typecheck !== undefined) {\n if (typeof typecheck !== \"boolean\") throw new Error(`${where}: typecheck must be boolean`);\n out.typecheck = typecheck;\n }\n return { ...out, ...parseContracts(raw, where) };\n}\n\nexport function isContractsMode(v: unknown): v is ContractsMode {\n return v === \"off\" || v === \"ensures\" || v === \"all\";\n}\n\nfunction parseContracts(raw: Record<string, unknown>, where: string): Partial<MutationCommon> {\n const contracts = raw[\"contracts\"];\n if (contracts === undefined) return {};\n if (!isContractsMode(contracts)) {\n throw new Error(`${where}: contracts must be \"off\", \"ensures\" or \"all\"`);\n }\n return { contracts };\n}\n\nfunction parseOutputs(raw: Record<string, unknown>, where: string): Partial<MutationCommon> {\n const out: Partial<MutationCommon> = {};\n const thresholds = raw[\"thresholds\"];\n if (thresholds !== undefined) {\n if (!isRecord(thresholds)) throw new Error(`${where}: thresholds must be { break }`);\n const brk = optionalNumber(thresholds[\"break\"], `${where}: thresholds.break`);\n out.thresholds = brk === undefined ? {} : { break: brk };\n }\n const report = optionalString(raw[\"report\"], `${where}: report`);\n if (report !== undefined) out.report = report;\n const decisions = optionalString(raw[\"decisions\"], `${where}: decisions`);\n if (decisions !== undefined) out.decisions = decisions;\n return out;\n}\n\nfunction parseCommon(raw: Record<string, unknown>, where: string): MutationCommon {\n return { ...parseRun(raw, where), ...parseMutants(raw, where), ...parseOutputs(raw, where) };\n}\n\n/** Validate a loaded config's default export. `where` names the file in errors. */\nexport function parseMutationConfig(raw: unknown, where: string): MutationConfig {\n if (!isRecord(raw)) throw new Error(`${where}: the default export must be a config object`);\n // targets, then runner/tests, then the rest: the order a rendered config reads in.\n const { targets, ...common } = parseCommon(raw, where);\n const runner = raw[\"runner\"];\n if (isRecord(runner) && runner[\"kind\"] === \"command\") {\n const argv = strings(runner[\"argv\"], `${where}: runner.argv`);\n if (argv.length === 0) throw new Error(`${where}: runner.argv is empty`);\n return { targets, runner: { kind: \"command\", argv }, ...common };\n }\n if (runner !== undefined && !(isRecord(runner) && runner[\"kind\"] === \"bun-test\")) {\n throw new Error(`${where}: runner.kind must be \"bun-test\" or \"command\"`);\n }\n const tests = raw[\"tests\"];\n if (!isRecord(tests)) throw new Error(`${where}: tests.include is required`);\n const include = strings(tests[\"include\"], `${where}: tests.include`);\n if (include.length === 0) throw new Error(`${where}: tests.include is empty`);\n const preload =\n tests[\"preload\"] === undefined\n ? undefined\n : strings(tests[\"preload\"], `${where}: tests.preload`);\n return { targets, tests: preload ? { include, preload } : { include }, ...common };\n}\n\n/** Import a `polly.mutate.ts` (or `.js`) and validate its default export. */\nexport async function loadMutationConfig(path: string): Promise<MutationConfig> {\n const mod: unknown = await import(path);\n const raw = isRecord(mod) ? mod[\"default\"] : undefined;\n return parseMutationConfig(raw, path);\n}\n"
9
11
  ],
10
- "mappings": "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",
11
- "debugId": "5C2B3898BBAEBE0B64756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "3F48DAA595C6EBD164756E2164756E21",
12
14
  "names": []
13
15
  }
@@ -1,30 +1,47 @@
1
1
  /**
2
- * @fairfox/polly/test/coverage — Stryker mutate-/test-target validation.
2
+ * @fairfox/polly/test/coverage — mutation target / test-glob validation.
3
3
  *
4
- * A Stryker config's `mutate` and `testFiles` lists are hand-curated. A path
5
- * that is renamed, or a glob whose directory moves, silently resolves to
6
- * nothing — Stryker mutates fewer files (or none) and the only signal is a
7
- * long mutation run whose score quietly drops. This asserts, in milliseconds,
8
- * that every entry still resolves to at least one file.
4
+ * A mutation config's target and test lists are hand-curated. A path that is
5
+ * renamed, or a glob whose directory moves, silently resolves to nothing —
6
+ * fewer files (or none) are mutated and the only signal is a long mutation
7
+ * run whose score quietly drops. This asserts, in milliseconds, that every
8
+ * entry still resolves to at least one file.
9
9
  *
10
- * Discovers configs two ways, covering both layouts in the wild: a single
11
- * `stryker.conf.json` at the root, and a `stryker/` directory of per-package
12
- * shards.
10
+ * Reads both config formats: `polly.mutate.ts` at the root, and Stryker JSON
11
+ * (deprecated) — a single `stryker.conf.json` at the root or a `stryker/`
12
+ * directory of per-package shards.
13
13
  */
14
14
  export interface MutateTargetIssue {
15
15
  config: string;
16
- field: "mutate" | "testFiles";
16
+ /** `mutate`/`testFiles` for a Stryker config, `targets`/`tests.include` for polly.mutate.ts. */
17
+ field: "mutate" | "testFiles" | "targets" | "tests.include";
17
18
  pattern: string;
18
19
  }
20
+ /** One mutation config, reduced to the paths it names. */
21
+ export interface MutateConfigEntry {
22
+ path: string;
23
+ format: "polly" | "stryker";
24
+ targets: {
25
+ field: MutateTargetIssue["field"];
26
+ patterns: string[];
27
+ };
28
+ tests: {
29
+ field: MutateTargetIssue["field"];
30
+ patterns: string[];
31
+ };
32
+ }
19
33
  export interface MutateTargetReport {
20
34
  configs: string[];
21
35
  issues: MutateTargetIssue[];
22
36
  }
23
37
  /** Locate every Stryker config under the project root. */
24
38
  export declare function findStrykerConfigs(root: string): Promise<string[]>;
39
+ /** Every mutation config under `root`, both formats, with the paths each names. */
40
+ export declare function findMutateConfigs(root: string): Promise<MutateConfigEntry[]>;
25
41
  /**
26
- * Validate every Stryker config under `root`. Paths in a Stryker config are
42
+ * Validate every mutation config under `root`. polly.mutate.ts paths are
43
+ * relative to its own directory (the root). Paths in a Stryker config are
27
44
  * relative to that config's directory; for the monorepo-root configs that is
28
- * the root, so we resolve globs against `root`.
45
+ * the root, so Stryker globs resolve against `root`.
29
46
  */
30
47
  export declare function validateMutateTargets(root: string): Promise<MutateTargetReport>;
@@ -431,8 +431,8 @@ function parse(path) {
431
431
  var ret = { root: "", dir: "", base: "", ext: "", name: "" };
432
432
  if (path.length === 0)
433
433
  return ret;
434
- var code = path.charCodeAt(0), isAbsolute2 = code === 47, start;
435
- if (isAbsolute2)
434
+ var code = path.charCodeAt(0), isAbsolute = code === 47, start;
435
+ if (isAbsolute)
436
436
  ret.root = "/", start = 1;
437
437
  else
438
438
  start = 0;
@@ -457,12 +457,12 @@ function parse(path) {
457
457
  }
458
458
  if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
459
459
  if (end !== -1)
460
- if (startPart === 0 && isAbsolute2)
460
+ if (startPart === 0 && isAbsolute)
461
461
  ret.base = ret.name = path.slice(1, end);
462
462
  else
463
463
  ret.base = ret.name = path.slice(startPart, end);
464
464
  } else {
465
- if (startPart === 0 && isAbsolute2)
465
+ if (startPart === 0 && isAbsolute)
466
466
  ret.name = path.slice(1, startDot), ret.base = path.slice(1, end);
467
467
  else
468
468
  ret.name = path.slice(startPart, startDot), ret.base = path.slice(startPart, end);
@@ -470,7 +470,7 @@ function parse(path) {
470
470
  }
471
471
  if (startPart > 0)
472
472
  ret.dir = path.slice(0, startPart - 1);
473
- else if (isAbsolute2)
473
+ else if (isAbsolute)
474
474
  ret.dir = "/";
475
475
  return ret;
476
476
  }
@@ -1238,4 +1238,4 @@ export {
1238
1238
  withRelay
1239
1239
  };
1240
1240
 
1241
- //# debugId=9DD3BC71E0F60DB064756E2164756E21
1241
+ //# debugId=83EA19F35D0E71EA64756E2164756E21