@reportforge/playwright-pdf 0.3.1 → 0.4.1

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/index.mjs CHANGED
@@ -1229,13 +1229,13 @@ var require_ast = __commonJS({
1229
1229
  helperExpression: function helperExpression(node) {
1230
1230
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
1231
1231
  },
1232
- scopedId: function scopedId(path12) {
1233
- return /^\.|this\b/.test(path12.original);
1232
+ scopedId: function scopedId(path15) {
1233
+ return /^\.|this\b/.test(path15.original);
1234
1234
  },
1235
1235
  // an ID is simple if it only has one part, and that part is not
1236
1236
  // `..` or `this`.
1237
- simpleId: function simpleId(path12) {
1238
- return path12.parts.length === 1 && !AST.helpers.scopedId(path12) && !path12.depth;
1237
+ simpleId: function simpleId(path15) {
1238
+ return path15.parts.length === 1 && !AST.helpers.scopedId(path15) && !path15.depth;
1239
1239
  }
1240
1240
  }
1241
1241
  };
@@ -2309,12 +2309,12 @@ var require_helpers2 = __commonJS({
2309
2309
  loc
2310
2310
  };
2311
2311
  }
2312
- function prepareMustache(path12, params, hash, open, strip, locInfo) {
2312
+ function prepareMustache(path15, params, hash, open, strip, locInfo) {
2313
2313
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
2314
2314
  var decorator = /\*/.test(open);
2315
2315
  return {
2316
2316
  type: decorator ? "Decorator" : "MustacheStatement",
2317
- path: path12,
2317
+ path: path15,
2318
2318
  params,
2319
2319
  hash,
2320
2320
  escaped,
@@ -2634,9 +2634,9 @@ var require_compiler = __commonJS({
2634
2634
  },
2635
2635
  DecoratorBlock: function DecoratorBlock(decorator) {
2636
2636
  var program = decorator.program && this.compileProgram(decorator.program);
2637
- var params = this.setupFullMustacheParams(decorator, program, void 0), path12 = decorator.path;
2637
+ var params = this.setupFullMustacheParams(decorator, program, void 0), path15 = decorator.path;
2638
2638
  this.useDecorators = true;
2639
- this.opcode("registerDecorator", params.length, path12.original);
2639
+ this.opcode("registerDecorator", params.length, path15.original);
2640
2640
  },
2641
2641
  PartialStatement: function PartialStatement(partial) {
2642
2642
  this.usePartial = true;
@@ -2700,46 +2700,46 @@ var require_compiler = __commonJS({
2700
2700
  }
2701
2701
  },
2702
2702
  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
2703
- var path12 = sexpr.path, name = path12.parts[0], isBlock = program != null || inverse != null;
2704
- this.opcode("getContext", path12.depth);
2703
+ var path15 = sexpr.path, name = path15.parts[0], isBlock = program != null || inverse != null;
2704
+ this.opcode("getContext", path15.depth);
2705
2705
  this.opcode("pushProgram", program);
2706
2706
  this.opcode("pushProgram", inverse);
2707
- path12.strict = true;
2708
- this.accept(path12);
2707
+ path15.strict = true;
2708
+ this.accept(path15);
2709
2709
  this.opcode("invokeAmbiguous", name, isBlock);
2710
2710
  },
2711
2711
  simpleSexpr: function simpleSexpr(sexpr) {
2712
- var path12 = sexpr.path;
2713
- path12.strict = true;
2714
- this.accept(path12);
2712
+ var path15 = sexpr.path;
2713
+ path15.strict = true;
2714
+ this.accept(path15);
2715
2715
  this.opcode("resolvePossibleLambda");
2716
2716
  },
2717
2717
  helperSexpr: function helperSexpr(sexpr, program, inverse) {
2718
- var params = this.setupFullMustacheParams(sexpr, program, inverse), path12 = sexpr.path, name = path12.parts[0];
2718
+ var params = this.setupFullMustacheParams(sexpr, program, inverse), path15 = sexpr.path, name = path15.parts[0];
2719
2719
  if (this.options.knownHelpers[name]) {
2720
2720
  this.opcode("invokeKnownHelper", params.length, name);
2721
2721
  } else if (this.options.knownHelpersOnly) {
2722
2722
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
2723
2723
  } else {
2724
- path12.strict = true;
2725
- path12.falsy = true;
2726
- this.accept(path12);
2727
- this.opcode("invokeHelper", params.length, path12.original, _ast2["default"].helpers.simpleId(path12));
2724
+ path15.strict = true;
2725
+ path15.falsy = true;
2726
+ this.accept(path15);
2727
+ this.opcode("invokeHelper", params.length, path15.original, _ast2["default"].helpers.simpleId(path15));
2728
2728
  }
2729
2729
  },
2730
- PathExpression: function PathExpression(path12) {
2731
- this.addDepth(path12.depth);
2732
- this.opcode("getContext", path12.depth);
2733
- var name = path12.parts[0], scoped = _ast2["default"].helpers.scopedId(path12), blockParamId = !path12.depth && !scoped && this.blockParamIndex(name);
2730
+ PathExpression: function PathExpression(path15) {
2731
+ this.addDepth(path15.depth);
2732
+ this.opcode("getContext", path15.depth);
2733
+ var name = path15.parts[0], scoped = _ast2["default"].helpers.scopedId(path15), blockParamId = !path15.depth && !scoped && this.blockParamIndex(name);
2734
2734
  if (blockParamId) {
2735
- this.opcode("lookupBlockParam", blockParamId, path12.parts);
2735
+ this.opcode("lookupBlockParam", blockParamId, path15.parts);
2736
2736
  } else if (!name) {
2737
2737
  this.opcode("pushContext");
2738
- } else if (path12.data) {
2738
+ } else if (path15.data) {
2739
2739
  this.options.data = true;
2740
- this.opcode("lookupData", path12.depth, path12.parts, path12.strict);
2740
+ this.opcode("lookupData", path15.depth, path15.parts, path15.strict);
2741
2741
  } else {
2742
- this.opcode("lookupOnContext", path12.parts, path12.falsy, path12.strict, scoped);
2742
+ this.opcode("lookupOnContext", path15.parts, path15.falsy, path15.strict, scoped);
2743
2743
  }
2744
2744
  },
2745
2745
  StringLiteral: function StringLiteral(string) {
@@ -3095,16 +3095,16 @@ var require_util = __commonJS({
3095
3095
  }
3096
3096
  exports.urlGenerate = urlGenerate;
3097
3097
  function normalize(aPath) {
3098
- var path12 = aPath;
3098
+ var path15 = aPath;
3099
3099
  var url = urlParse(aPath);
3100
3100
  if (url) {
3101
3101
  if (!url.path) {
3102
3102
  return aPath;
3103
3103
  }
3104
- path12 = url.path;
3104
+ path15 = url.path;
3105
3105
  }
3106
- var isAbsolute = exports.isAbsolute(path12);
3107
- var parts = path12.split(/\/+/);
3106
+ var isAbsolute = exports.isAbsolute(path15);
3107
+ var parts = path15.split(/\/+/);
3108
3108
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
3109
3109
  part = parts[i];
3110
3110
  if (part === ".") {
@@ -3121,15 +3121,15 @@ var require_util = __commonJS({
3121
3121
  }
3122
3122
  }
3123
3123
  }
3124
- path12 = parts.join("/");
3125
- if (path12 === "") {
3126
- path12 = isAbsolute ? "/" : ".";
3124
+ path15 = parts.join("/");
3125
+ if (path15 === "") {
3126
+ path15 = isAbsolute ? "/" : ".";
3127
3127
  }
3128
3128
  if (url) {
3129
- url.path = path12;
3129
+ url.path = path15;
3130
3130
  return urlGenerate(url);
3131
3131
  }
3132
- return path12;
3132
+ return path15;
3133
3133
  }
3134
3134
  exports.normalize = normalize;
3135
3135
  function join(aRoot, aPath) {
@@ -5932,8 +5932,8 @@ var require_printer = __commonJS({
5932
5932
  return this.accept(sexpr.path) + " " + params + hash;
5933
5933
  };
5934
5934
  PrintVisitor.prototype.PathExpression = function(id) {
5935
- var path12 = id.parts.join("/");
5936
- return (id.data ? "@" : "") + "PATH:" + path12;
5935
+ var path15 = id.parts.join("/");
5936
+ return (id.data ? "@" : "") + "PATH:" + path15;
5937
5937
  };
5938
5938
  PrintVisitor.prototype.StringLiteral = function(string) {
5939
5939
  return '"' + string.value + '"';
@@ -5974,8 +5974,8 @@ var require_lib = __commonJS({
5974
5974
  handlebars.print = printer.print;
5975
5975
  module.exports = handlebars;
5976
5976
  function extension(module2, filename) {
5977
- var fs12 = __require("fs");
5978
- var templateString = fs12.readFileSync(filename, "utf8");
5977
+ var fs14 = __require("fs");
5978
+ var templateString = fs14.readFileSync(filename, "utf8");
5979
5979
  module2.exports = handlebars.compile(templateString);
5980
5980
  }
5981
5981
  if (typeof __require !== "undefined" && __require.extensions) {
@@ -6004,7 +6004,7 @@ var require_util2 = __commonJS({
6004
6004
  "node_modules/chrome-finder/lib/util.js"(exports, module) {
6005
6005
  "use strict";
6006
6006
  init_esm_shims();
6007
- var fs12 = __require("fs");
6007
+ var fs14 = __require("fs");
6008
6008
  var newLineRegex = /\r?\n/;
6009
6009
  function sort(installations, priorities) {
6010
6010
  const defaultPriority = 10;
@@ -6022,7 +6022,7 @@ var require_util2 = __commonJS({
6022
6022
  return false;
6023
6023
  }
6024
6024
  try {
6025
- fs12.accessSync(file);
6025
+ fs14.accessSync(file);
6026
6026
  return true;
6027
6027
  } catch (e) {
6028
6028
  return false;
@@ -6033,7 +6033,7 @@ var require_util2 = __commonJS({
6033
6033
  return false;
6034
6034
  }
6035
6035
  try {
6036
- var stat = fs12.statSync(file);
6036
+ var stat = fs14.statSync(file);
6037
6037
  return stat && typeof stat.isFile === "function" && stat.isFile();
6038
6038
  } catch (e) {
6039
6039
  return false;
@@ -6053,7 +6053,7 @@ var require_win32 = __commonJS({
6053
6053
  "node_modules/chrome-finder/lib/win32.js"(exports, module) {
6054
6054
  "use strict";
6055
6055
  init_esm_shims();
6056
- var path12 = __require("path");
6056
+ var path15 = __require("path");
6057
6057
  var { canAccess } = require_util2();
6058
6058
  function win32() {
6059
6059
  const installations = [];
@@ -6067,7 +6067,7 @@ var require_win32 = __commonJS({
6067
6067
  const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]];
6068
6068
  prefixes.forEach((prefix) => suffixes.forEach((suffix) => {
6069
6069
  if (prefix) {
6070
- const chromePath = path12.join(prefix, suffix);
6070
+ const chromePath = path15.join(prefix, suffix);
6071
6071
  if (canAccess(chromePath)) {
6072
6072
  installations.push(chromePath);
6073
6073
  }
@@ -6085,7 +6085,7 @@ var require_darwin = __commonJS({
6085
6085
  "use strict";
6086
6086
  init_esm_shims();
6087
6087
  var { execSync: execSync3 } = __require("child_process");
6088
- var path12 = __require("path");
6088
+ var path15 = __require("path");
6089
6089
  var { canAccess, newLineRegex, sort } = require_util2();
6090
6090
  function darwin() {
6091
6091
  const suffixes = ["/Contents/MacOS/Google Chrome Canary", "/Contents/MacOS/Google Chrome", "/Contents/MacOS/Chromium"];
@@ -6095,7 +6095,7 @@ var require_darwin = __commonJS({
6095
6095
  ${LSREGISTER} -dump | grep -E -i -o '/.+(google chrome( canary)?|chromium)\\.app(\\s|$)' | grep -E -v 'Caches|TimeMachine|Temporary|/Volumes|\\.Trash'
6096
6096
  `).toString().split(newLineRegex).forEach((inst) => {
6097
6097
  suffixes.forEach((suffix) => {
6098
- const execPath = path12.join(inst.trim(), suffix);
6098
+ const execPath = path15.join(inst.trim(), suffix);
6099
6099
  if (canAccess(execPath)) {
6100
6100
  installations.push(execPath);
6101
6101
  }
@@ -6124,8 +6124,8 @@ var require_linux = __commonJS({
6124
6124
  "use strict";
6125
6125
  init_esm_shims();
6126
6126
  var { execSync: execSync3, execFileSync: execFileSync2 } = __require("child_process");
6127
- var path12 = __require("path");
6128
- var fs12 = __require("fs");
6127
+ var path15 = __require("path");
6128
+ var fs14 = __require("fs");
6129
6129
  var { canAccess, sort, isExecutable, newLineRegex } = require_util2();
6130
6130
  function findChromeExecutablesForLinuxDesktop(folder) {
6131
6131
  const argumentsRegex = /(^[^ ]+).*/;
@@ -6142,7 +6142,7 @@ var require_linux = __commonJS({
6142
6142
  function linux() {
6143
6143
  let installations = [];
6144
6144
  const desktopInstallationFolders = [
6145
- path12.join(__require("os").homedir(), ".local/share/applications/"),
6145
+ path15.join(__require("os").homedir(), ".local/share/applications/"),
6146
6146
  "/usr/share/applications/"
6147
6147
  ];
6148
6148
  desktopInstallationFolders.forEach((folder) => {
@@ -6168,7 +6168,7 @@ var require_linux = __commonJS({
6168
6168
  ].map((possiblePath) => {
6169
6169
  try {
6170
6170
  const chromePathToTest = possiblePath + "/" + executable;
6171
- if (fs12.existsSync(chromePathToTest) && canAccess(chromePathToTest) && isExecutable(chromePathToTest)) {
6171
+ if (fs14.existsSync(chromePathToTest) && canAccess(chromePathToTest) && isExecutable(chromePathToTest)) {
6172
6172
  installations.push(chromePathToTest);
6173
6173
  return chromePathToTest;
6174
6174
  }
@@ -6750,8 +6750,8 @@ function getErrorMap() {
6750
6750
  // node_modules/zod/v3/helpers/parseUtil.js
6751
6751
  init_esm_shims();
6752
6752
  var makeIssue = (params) => {
6753
- const { data, path: path12, errorMaps, issueData } = params;
6754
- const fullPath = [...path12, ...issueData.path || []];
6753
+ const { data, path: path15, errorMaps, issueData } = params;
6754
+ const fullPath = [...path15, ...issueData.path || []];
6755
6755
  const fullIssue = {
6756
6756
  ...issueData,
6757
6757
  path: fullPath
@@ -6871,11 +6871,11 @@ var errorUtil;
6871
6871
 
6872
6872
  // node_modules/zod/v3/types.js
6873
6873
  var ParseInputLazyPath = class {
6874
- constructor(parent, value, path12, key) {
6874
+ constructor(parent, value, path15, key) {
6875
6875
  this._cachedPath = [];
6876
6876
  this.parent = parent;
6877
6877
  this.data = value;
6878
- this._path = path12;
6878
+ this._path = path15;
6879
6879
  this._key = key;
6880
6880
  }
6881
6881
  get path() {
@@ -10355,6 +10355,16 @@ var notifyConfigSchema = external_exports.object({
10355
10355
  discord: channelConfigSchema.optional(),
10356
10356
  email: emailChannelConfigSchema.optional()
10357
10357
  }).optional();
10358
+ var failureAnalysisConfigSchema = external_exports.object({
10359
+ enabled: external_exports.boolean().default(true),
10360
+ maxClusters: external_exports.number().int().min(1).max(50).default(10),
10361
+ // Qualitative floor (not a numeric % — consistent with the match-strength design).
10362
+ minStrength: external_exports.enum(["weak", "moderate", "strong"]).default("weak"),
10363
+ maxFailuresToAnalyse: external_exports.number().int().min(1).default(500),
10364
+ // Consumed by the Plan 3 feedback collector; inert until then.
10365
+ collectUnclassified: external_exports.boolean().default(true),
10366
+ autoUpdateModel: external_exports.boolean().default(true)
10367
+ }).default({});
10358
10368
  var ReporterOptionsSchema = external_exports.object({
10359
10369
  outputFile: external_exports.string().optional().default(DEFAULT_OPTIONS.outputFile),
10360
10370
  template: external_exports.union([
@@ -10384,6 +10394,7 @@ var ReporterOptionsSchema = external_exports.object({
10384
10394
  historySize: external_exports.number().int().min(2).optional().default(DEFAULT_OPTIONS.historySize),
10385
10395
  showTrend: external_exports.boolean().optional().default(DEFAULT_OPTIONS.showTrend),
10386
10396
  flakinessTopN: external_exports.number().int().min(0, "flakinessTopN must be 0 or greater").optional().default(DEFAULT_OPTIONS.flakinessTopN),
10397
+ failureAnalysis: failureAnalysisConfigSchema,
10387
10398
  templatePath: external_exports.union([
10388
10399
  external_exports.string().regex(/\.hbs$/, "templatePath must end with .hbs").refine((v) => v.slice(0, -4).trim().length > 0, "templatePath basename must not be empty"),
10389
10400
  external_exports.array(
@@ -10623,6 +10634,17 @@ function clearCache() {
10623
10634
  init_esm_shims();
10624
10635
  var LICENSE_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAbvknstk0gxoJ7UwATdlxR8btxvO7u7RdpO1uV90PtuE=\n-----END PUBLIC KEY-----";
10625
10636
 
10637
+ // src/analysis/ModelStore.ts
10638
+ init_esm_shims();
10639
+ import fs3 from "fs";
10640
+ import os4 from "os";
10641
+ import path4 from "path";
10642
+ import { createPublicKey as createPublicKey2, verify as verify2 } from "crypto";
10643
+
10644
+ // src/analysis/model-bundle.ts
10645
+ init_esm_shims();
10646
+ var MODEL = { "alpha": 1, "labels": ["assertion", "env-config", "flaky-by-retry", "locator-not-found", "navigation", "network", "timeout"], "logLikelihoods": { "assertion": { "NUM": -4.574710978503383, "assertionerror": -4.980176086611547, "be": -4.980176086611547, "cart": -4.980176086611547, "checkout": -4.980176086611547, "com": -4.980176086611547, "dashboard": -4.980176086611547, "deep": -4.980176086611547, "equal": -4.980176086611547, "equality": -4.980176086611547, "error": -4.980176086611547, "expect": -3.5938817254916566, "expected": -3.1083739097099556, "failed": -4.980176086611547, "false": -4.980176086611547, "got": -4.980176086611547, "id": -4.574710978503383, "john": -4.980176086611547, "locator": -4.574710978503383, "login": -4.980176086611547, "must": -4.980176086611547, "page": -4.980176086611547, "pattern": -4.980176086611547, "received": -3.275427994373122, "string": -4.980176086611547, "to": -4.980176086611547, "tobe": -4.980176086611547, "tobetruthy": -4.980176086611547, "toequal": -4.980176086611547, "tohavecount": -4.980176086611547, "tohavetext": -4.980176086611547, "tohavetitle": -4.980176086611547, "tohavevalue": -4.980176086611547, "truthy": -4.980176086611547, "value": -4.980176086611547 }, "env-config": { "NUM": -4.997212273764115, "api": -4.997212273764115, "at": -4.997212273764115, "back": -4.997212273764115, "base": -4.997212273764115, "baseurl": -4.997212273764115, "browsers": -4.997212273764115, "cannot": -4.997212273764115, "ci": -4.997212273764115, "config": -4.304065093204169, "directory": -4.591747165655951, "dotenv": -4.997212273764115, "enoent": -4.997212273764115, "env": -4.591747165655951, "environment": -4.591747165655951, "error": -4.08092154188996, "existent": -4.997212273764115, "failed": -4.997212273764115, "falling": -4.997212273764115, "file": -4.997212273764115, "find": -4.997212273764115, "from": -4.997212273764115, "in": -4.591747165655951, "invalid": -4.997212273764115, "is": -4.997212273764115, "json": -4.591747165655951, "key": -4.997212273764115, "load": -4.997212273764115, "missing": -4.997212273764115, "module": -4.997212273764115, "no": -4.997212273764115, "non": -4.997212273764115, "not": -4.997212273764115, "open": -4.997212273764115, "or": -4.997212273764115, "path": -4.997212273764115, "playwright": -4.591747165655951, "points": -4.997212273764115, "position": -4.997212273764115, "required": -4.997212273764115, "set": -4.997212273764115, "spec": -4.997212273764115, "such": -4.997212273764115, "syntaxerror": -4.997212273764115, "test": -4.997212273764115, "to": -4.304065093204169, "token": -4.997212273764115, "ts": -4.997212273764115, "tsconfig": -4.997212273764115, "undefined": -4.591747165655951, "unexpected": -4.997212273764115, "url": -4.997212273764115, "values": -4.997212273764115, "variable": -4.591747165655951 }, "flaky-by-retry": { "after": -4.58836306767171, "and": -4.993828175779875, "animation": -4.993828175779875, "attempt": -4.07753744390572, "between": -4.993828175779875, "blip": -4.993828175779875, "cause": -4.993828175779875, "change": -4.993828175779875, "click": -4.993828175779875, "code": -4.993828175779875, "commit": -4.993828175779875, "condition": -4.993828175779875, "dependency": -4.993828175779875, "detached": -4.993828175779875, "deterministic": -4.993828175779875, "element": -4.993828175779875, "failed": -4.300680995219929, "fails": -4.993828175779875, "flaky": -4.07753744390572, "green": -4.993828175779875, "in": -4.993828175779875, "intermittent": -4.993828175779875, "locator": -4.993828175779875, "marked": -4.993828175779875, "network": -4.993828175779875, "no": -4.993828175779875, "non": -4.993828175779875, "on": -3.895215887111765, "order": -4.993828175779875, "passed": -3.895215887111765, "quarantined": -4.993828175779875, "race": -4.993828175779875, "reproducible": -4.993828175779875, "rerun": -4.993828175779875, "retry": -4.07753744390572, "runs": -4.993828175779875, "same": -4.993828175779875, "second": -4.993828175779875, "stale": -4.993828175779875, "succeeded": -4.993828175779875, "test": -4.58836306767171, "the": -4.993828175779875, "then": -4.993828175779875, "timeout": -4.993828175779875, "unstable": -4.993828175779875, "with": -4.993828175779875, "without": -4.993828175779875 }, "locator-not-found": { "an": -4.993828175779875, "attached": -4.993828175779875, "btn": -4.993828175779875, "button": -4.58836306767171, "cart": -4.993828175779875, "checkout": -4.993828175779875, "click": -4.993828175779875, "data": -4.993828175779875, "div": -4.993828175779875, "dom": -4.993828175779875, "element": -4.07753744390572, "elements": -4.300680995219929, "error": -4.58836306767171, "find": -4.993828175779875, "for": -4.58836306767171, "found": -4.58836306767171, "getbyrole": -4.993828175779875, "getbytext": -4.993828175779875, "icon": -4.993828175779875, "id": -4.993828175779875, "is": -4.993828175779875, "locator": -4.07753744390572, "matched": -4.993828175779875, "matches": -4.993828175779875, "missing": -4.993828175779875, "mode": -4.58836306767171, "name": -4.993828175779875, "no": -4.58836306767171, "node": -4.993828175779875, "not": -4.58836306767171, "resolved": -4.58836306767171, "save": -4.993828175779875, "selector": -4.58836306767171, "strict": -4.58836306767171, "submit": -4.993828175779875, "testid": -4.993828175779875, "textcontent": -4.993828175779875, "the": -4.58836306767171, "to": -4.07753744390572, "total": -4.993828175779875, "unable": -4.993828175779875, "violation": -4.58836306767171, "waiting": -4.993828175779875, "with": -4.993828175779875, "xpath": -4.993828175779875 }, "navigation": { "NUM": -4.983606621708336, "aborted": -4.983606621708336, "another": -4.983606621708336, "back": -4.983606621708336, "because": -4.578141513600172, "by": -4.578141513600172, "checkout": -4.983606621708336, "context": -4.983606621708336, "crashed": -4.983606621708336, "dashboard": -4.983606621708336, "destroyed": -4.983606621708336, "detached": -4.983606621708336, "entry": -4.983606621708336, "err": -4.578141513600172, "exceeded": -4.983606621708336, "execution": -4.983606621708336, "failed": -4.983606621708336, "frame": -4.578141513600172, "goback": -4.983606621708336, "goto": -4.290459441148391, "history": -4.983606621708336, "home": -4.983606621708336, "interrupted": -4.578141513600172, "is": -4.983606621708336, "likely": -4.983606621708336, "login": -4.983606621708336, "many": -4.983606621708336, "maybe": -4.983606621708336, "most": -4.983606621708336, "navigate": -4.983606621708336, "navigating": -4.983606621708336, "navigation": -3.7308436532129683, "net": -4.578141513600172, "no": -4.983606621708336, "of": -4.983606621708336, "page": -3.884994333040227, "redirects": -4.983606621708336, "timeout": -4.983606621708336, "to": -3.7308436532129683, "too": -4.983606621708336, "waitforurl": -4.983606621708336, "was": -4.578141513600172 }, "network": { "NUM": -4.57126863431241, "URL": -4.283586561860629, "again": -4.976733742420574, "api": -4.976733742420574, "apirequestcontext": -4.976733742420574, "at": -4.976733742420574, "body": -4.976733742420574, "connect": -4.976733742420574, "connection": -4.57126863431241, "disconnected": -4.976733742420574, "eai": -4.976733742420574, "econnrefused": -4.976733742420574, "err": -3.8781214537524646, "error": -4.976733742420574, "failed": -4.283586561860629, "fetch": -4.976733742420574, "fetching": -4.976733742420574, "from": -4.976733742420574, "get": -4.976733742420574, "getaddrinfo": -4.976733742420574, "goto": -4.976733742420574, "hang": -4.976733742420574, "internet": -4.976733742420574, "loading": -4.976733742420574, "name": -4.976733742420574, "net": -3.8781214537524646, "not": -4.976733742420574, "on": -4.976733742420574, "out": -4.976733742420574, "page": -4.57126863431241, "products": -4.976733742420574, "protocol": -4.976733742420574, "reading": -4.976733742420574, "reason": -4.976733742420574, "refused": -4.976733742420574, "request": -4.57126863431241, "resolved": -4.976733742420574, "resource": -4.976733742420574, "response": -4.976733742420574, "route": -4.976733742420574, "socket": -4.976733742420574, "ssl": -4.976733742420574, "timed": -4.976733742420574, "to": -4.976733742420574, "up": -4.976733742420574, "upstream": -4.976733742420574, "while": -4.976733742420574 }, "timeout": { "NUM": -3.4965075614664802, "after": -5.000584958242754, "be": -4.59511985013459, "cart": -5.000584958242754, "click": -5.000584958242754, "element": -5.000584958242754, "event": -5.000584958242754, "exceeded": -3.6142905971228636, "expect": -5.000584958242754, "fill": -5.000584958242754, "for": -3.7478219897473863, "function": -5.000584958242754, "load": -5.000584958242754, "locator": -4.084294226368599, "navigation": -5.000584958242754, "of": -5.000584958242754, "out": -5.000584958242754, "page": -4.59511985013459, "running": -5.000584958242754, "selector": -5.000584958242754, "submit": -5.000584958242754, "test": -4.59511985013459, "timed": -5.000584958242754, "timeout": -3.6142905971228636, "timeouterror": -4.59511985013459, "to": -4.59511985013459, "tobevisible": -5.000584958242754, "visible": -4.59511985013459, "waitforloadstate": -5.000584958242754, "waitforselector": -5.000584958242754, "waiting": -3.7478219897473863, "while": -5.000584958242754 } }, "playwrightVersion": "1.59.1", "priors": { "assertion": -1.9636097261547143, "env-config": -1.9636097261547143, "flaky-by-retry": -1.9636097261547143, "locator-not-found": -1.9636097261547143, "navigation": -1.9636097261547143, "network": -1.9636097261547143, "timeout": -1.845826690498331 }, "totals": { "assertion": 64, "env-config": 69, "flaky-by-retry": 68, "locator-not-found": 68, "navigation": 65, "network": 63, "timeout": 70 }, "version": 1, "vocabSize": 227 };
10647
+
10626
10648
  // src/utils/logger.ts
10627
10649
  init_esm_shims();
10628
10650
  var PREFIX = "[reportforge]";
@@ -10644,6 +10666,83 @@ ${JSON.stringify(data, null, 2)}` : "";
10644
10666
  }
10645
10667
  };
10646
10668
 
10669
+ // src/analysis/ModelStore.ts
10670
+ var MODEL_SIG_DOMAIN = "reportforge-model-sig:v1\n";
10671
+ var testDeps = null;
10672
+ function cacheFile() {
10673
+ return testDeps?.cacheFile ?? path4.join(os4.homedir(), ".reportforge", "model.json");
10674
+ }
10675
+ function publicKey() {
10676
+ const pem = testDeps?.publicKeyPem ?? LICENSE_PUBLIC_KEY_PEM;
10677
+ try {
10678
+ return pem && pem.includes("PUBLIC KEY") ? createPublicKey2(pem) : null;
10679
+ } catch {
10680
+ return null;
10681
+ }
10682
+ }
10683
+ function verifyModelSignature(modelJson, signatureB64url) {
10684
+ const key = publicKey();
10685
+ if (!key) return false;
10686
+ try {
10687
+ return verify2(null, Buffer.from(MODEL_SIG_DOMAIN + modelJson), key, Buffer.from(signatureB64url, "base64url"));
10688
+ } catch {
10689
+ return false;
10690
+ }
10691
+ }
10692
+ function schemaValid(m) {
10693
+ const x = m;
10694
+ return !!x && Array.isArray(x.labels) && x.labels.length > 0 && typeof x.version === "number" && !!x.priors && !!x.totals && !!x.logLikelihoods && typeof x.vocabSize === "number" && typeof x.alpha === "number";
10695
+ }
10696
+ function readCache2() {
10697
+ try {
10698
+ const raw = fs3.readFileSync(cacheFile(), "utf8");
10699
+ const c = JSON.parse(raw);
10700
+ if (typeof c.version === "number" && typeof c.modelJson === "string" && typeof c.signature === "string") {
10701
+ return c;
10702
+ }
10703
+ } catch {
10704
+ }
10705
+ return null;
10706
+ }
10707
+ function loadModel(autoUpdate) {
10708
+ if (!autoUpdate) return MODEL;
10709
+ const c = readCache2();
10710
+ if (!c || c.version <= MODEL.version) return MODEL;
10711
+ if (!verifyModelSignature(c.modelJson, c.signature)) {
10712
+ logger.debug(`cached model v${c.version} signature invalid \u2014 using bundled (possible tampering or key mismatch)`);
10713
+ return MODEL;
10714
+ }
10715
+ try {
10716
+ const m = JSON.parse(c.modelJson);
10717
+ if (schemaValid(m)) return m;
10718
+ logger.debug(`cached model v${c.version} failed schema validation \u2014 using bundled`);
10719
+ } catch {
10720
+ logger.debug(`cached model v${c.version} JSON parse failed \u2014 using bundled`);
10721
+ }
10722
+ return MODEL;
10723
+ }
10724
+ function acceptDeliveredModel(d) {
10725
+ try {
10726
+ const current = readCache2()?.version ?? MODEL.version;
10727
+ if (d.version <= current) return;
10728
+ if (!verifyModelSignature(d.modelJson, d.signature)) {
10729
+ logger.debug(`delivered model v${d.version} signature invalid \u2014 rejected`);
10730
+ return;
10731
+ }
10732
+ const m = JSON.parse(d.modelJson);
10733
+ if (!schemaValid(m)) {
10734
+ logger.debug(`delivered model v${d.version} failed schema validation \u2014 rejected`);
10735
+ return;
10736
+ }
10737
+ const file = cacheFile();
10738
+ fs3.mkdirSync(path4.dirname(file), { recursive: true });
10739
+ const tmp = `${file}.${process.pid}.tmp`;
10740
+ fs3.writeFileSync(tmp, JSON.stringify({ version: d.version, modelJson: d.modelJson, signature: d.signature }), { mode: 384 });
10741
+ fs3.renameSync(tmp, file);
10742
+ } catch {
10743
+ }
10744
+ }
10745
+
10647
10746
  // src/license/LicenseClient.ts
10648
10747
  var DEFAULT_SERVER_URL = "https://reportforge.org";
10649
10748
  var REFRESH_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
@@ -10657,6 +10756,8 @@ var LicenseClient = class {
10657
10756
  this.serverUrl = normalizeUrl(
10658
10757
  opts.serverUrl ?? process.env.REPORTFORGE_SERVER_URL ?? DEFAULT_SERVER_URL
10659
10758
  );
10759
+ this.autoUpdateModel = opts.autoUpdateModel ?? true;
10760
+ this.modelVersionForServer = this.autoUpdateModel ? opts.currentModelVersion ?? 0 : Number.MAX_SAFE_INTEGER;
10660
10761
  }
10661
10762
  async resolve() {
10662
10763
  const key = this.rawKey || process.env.RF_LICENSE_KEY?.trim();
@@ -10737,6 +10838,9 @@ var LicenseClient = class {
10737
10838
  machinesUsed: result.machinesUsed,
10738
10839
  machineLimit: result.machineLimit
10739
10840
  });
10841
+ if (this.autoUpdateModel && result.model) {
10842
+ acceptDeliveredModel(result.model);
10843
+ }
10740
10844
  return {
10741
10845
  plan: "subscription",
10742
10846
  source: "jwt-online",
@@ -10773,13 +10877,15 @@ var LicenseClient = class {
10773
10877
  return this.post("/api/license/activate", {
10774
10878
  licenseKey,
10775
10879
  fingerprint: this.fingerprint.hash,
10776
- label: this.fingerprint.label
10880
+ label: this.fingerprint.label,
10881
+ modelVersion: this.modelVersionForServer
10777
10882
  });
10778
10883
  }
10779
10884
  async refresh(jwt) {
10780
10885
  return this.post("/api/license/refresh", {
10781
10886
  jwt,
10782
- label: this.fingerprint.label
10887
+ label: this.fingerprint.label,
10888
+ modelVersion: this.modelVersionForServer
10783
10889
  });
10784
10890
  }
10785
10891
  async post(route, body) {
@@ -10837,9 +10943,292 @@ function normalizeUrl(url) {
10837
10943
  return url.endsWith("/") ? url.slice(0, -1) : url;
10838
10944
  }
10839
10945
 
10946
+ // src/analysis/index.ts
10947
+ init_esm_shims();
10948
+
10949
+ // src/analysis/FailureClusterer.ts
10950
+ init_esm_shims();
10951
+
10952
+ // src/analysis/NaiveBayesClassifier.ts
10953
+ init_esm_shims();
10954
+
10955
+ // src/analysis/tokenize.ts
10956
+ init_esm_shims();
10957
+ function tokenize(text) {
10958
+ return text.toLowerCase().replace(/https?:\/\/\S+/gi, " URL ").replace(/[^a-zA-Z0-9\s]/g, " ").replace(/\b\d{3,}[a-z]*\b/g, " NUM ").split(/\s+/).filter((t) => t.length > 1);
10959
+ }
10960
+
10961
+ // src/analysis/NaiveBayesClassifier.ts
10962
+ function classify(model, text) {
10963
+ const tokens = tokenize(text);
10964
+ const vocabTokens = tokens.filter(
10965
+ (tok) => model.labels.some((l) => model.logLikelihoods[l]?.[tok] !== void 0)
10966
+ );
10967
+ if (vocabTokens.length === 0) {
10968
+ return { label: "unknown", margin: 0 };
10969
+ }
10970
+ const scores = {};
10971
+ for (const label of model.labels) {
10972
+ let s = model.priors[label];
10973
+ const oov = Math.log(model.alpha / (model.totals[label] + model.alpha * model.vocabSize));
10974
+ const ll = model.logLikelihoods[label];
10975
+ for (const tok of vocabTokens) {
10976
+ s += ll[tok] ?? oov;
10977
+ }
10978
+ scores[label] = s;
10979
+ }
10980
+ const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
10981
+ const topLog = ranked[0][1];
10982
+ const secondLog = ranked[1]?.[1] ?? topLog;
10983
+ const margin = (topLog - secondLog) / vocabTokens.length;
10984
+ return { label: ranked[0][0], margin };
10985
+ }
10986
+
10987
+ // src/analysis/constants.ts
10988
+ init_esm_shims();
10989
+ var STRENGTH = {
10990
+ unknownMaxMargin: 0.05,
10991
+ collectMaxMargin: 0.1,
10992
+ harvestMinMargin: 0.25,
10993
+ moderateMinMargin: 0.1,
10994
+ strongMinMargin: 0.25
10995
+ };
10996
+ var STRENGTH_LEVELS = ["weak", "moderate", "strong"];
10997
+ var FLAKY_MARGIN = 999;
10998
+
10999
+ // src/analysis/FailureClusterer.ts
11000
+ var MAX_TESTS_PER_CLUSTER = 20;
11001
+ var MAX_MESSAGE_CHARS = 200;
11002
+ function strengthOf(margin) {
11003
+ if (margin >= STRENGTH.strongMinMargin) return "strong";
11004
+ if (margin >= STRENGTH.moderateMinMargin) return "moderate";
11005
+ return "weak";
11006
+ }
11007
+ function classifyInput(f) {
11008
+ const head = (f.error.stack ?? "").split("\n").slice(0, 3).join(" ");
11009
+ return `${f.error.message} ${head}`;
11010
+ }
11011
+ function topFrame(stack) {
11012
+ if (!stack) return void 0;
11013
+ for (const line of stack.split("\n")) {
11014
+ const t = line.trim();
11015
+ if (t.startsWith("at ") && !t.replace(/\\/g, "/").includes("node_modules/playwright")) return t;
11016
+ }
11017
+ return void 0;
11018
+ }
11019
+ function representativeMessage(message) {
11020
+ const firstLine = message.split("\n")[0];
11021
+ return firstLine.length > MAX_MESSAGE_CHARS ? `${firstLine.slice(0, MAX_MESSAGE_CHARS)}\u2026` : firstLine;
11022
+ }
11023
+ function classifyOne(f, model) {
11024
+ if (f.retryHistory.some((r) => r.status === "passed")) {
11025
+ return { category: "flaky-by-retry", margin: FLAKY_MARGIN, failure: f, frame: topFrame(f.error.stack) };
11026
+ }
11027
+ const { label, margin } = classify(model, classifyInput(f));
11028
+ return { category: label, margin, failure: f, frame: topFrame(f.error.stack) };
11029
+ }
11030
+ function classifyAll(failures, model) {
11031
+ return failures.map((f) => classifyOne(f, model));
11032
+ }
11033
+ function clusterClassified(classified) {
11034
+ const groups = /* @__PURE__ */ new Map();
11035
+ for (const c of classified) {
11036
+ const key = `${c.category}::${c.frame ?? "__no-stack__"}`;
11037
+ const bucket = groups.get(key);
11038
+ if (bucket) bucket.push(c);
11039
+ else groups.set(key, [c]);
11040
+ }
11041
+ const clusters = [];
11042
+ for (const members of groups.values()) {
11043
+ const margin = Math.max(...members.map((m) => m.margin));
11044
+ clusters.push({
11045
+ category: members[0].category,
11046
+ strength: strengthOf(margin),
11047
+ margin,
11048
+ count: members.length,
11049
+ tests: members.slice(0, MAX_TESTS_PER_CLUSTER).map((m) => m.failure.testTitle),
11050
+ testsTotal: members.length,
11051
+ representativeMessage: representativeMessage(members[0].failure.error.message),
11052
+ stackFrame: members[0].frame
11053
+ });
11054
+ }
11055
+ return clusters;
11056
+ }
11057
+
11058
+ // src/analysis/SummaryWriter.ts
11059
+ init_esm_shims();
11060
+ var CATEGORY_LABEL = {
11061
+ assertion: "Assertion failure",
11062
+ timeout: "Timeout",
11063
+ "locator-not-found": "Element not found",
11064
+ network: "Network error",
11065
+ navigation: "Navigation error",
11066
+ "env-config": "Environment / config",
11067
+ "flaky-by-retry": "Flaky (passed on retry)",
11068
+ unknown: "Unclassified"
11069
+ };
11070
+ var SHORT_LABEL = {
11071
+ assertion: "assertion",
11072
+ timeout: "timeout",
11073
+ "locator-not-found": "element not found",
11074
+ network: "network",
11075
+ navigation: "navigation",
11076
+ "env-config": "env/config",
11077
+ "flaky-by-retry": "flaky",
11078
+ unknown: "unclassified"
11079
+ };
11080
+ function buildOneLiner(clusters) {
11081
+ if (clusters.length === 0) return "";
11082
+ const byCategory = /* @__PURE__ */ new Map();
11083
+ for (const c of clusters) {
11084
+ byCategory.set(c.category, (byCategory.get(c.category) ?? 0) + c.count);
11085
+ }
11086
+ const head = `${clusters.length} root ${clusters.length === 1 ? "cause" : "causes"}`;
11087
+ const tally = [...byCategory.entries()].sort((a, b) => b[1] - a[1]).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`);
11088
+ return [head, ...tally].join(" \xB7 ");
11089
+ }
11090
+ function dominantCategory(clusters) {
11091
+ return clusters[0]?.category ?? "unknown";
11092
+ }
11093
+
11094
+ // src/analysis/UnclassifiedCollector.ts
11095
+ init_esm_shims();
11096
+ import fs4 from "fs";
11097
+ import os5 from "os";
11098
+ import path5 from "path";
11099
+
11100
+ // src/analysis/feedback-csv.ts
11101
+ init_esm_shims();
11102
+ var FEEDBACK_HEADER = "category,margin,text";
11103
+ function parseFeedbackRows(content) {
11104
+ return content.trim().split("\n").slice(1).filter(Boolean).map((line) => {
11105
+ const parts = line.split(",");
11106
+ return { category: parts[0], margin: parts[1], text: parts.slice(2).join(","), raw: line };
11107
+ });
11108
+ }
11109
+ function feedbackIdentity(category, text) {
11110
+ return `${category} ${text}`;
11111
+ }
11112
+
11113
+ // src/analysis/UnclassifiedCollector.ts
11114
+ var MAX_ROWS = 500;
11115
+ var FILENAME = "unclassified.csv";
11116
+ var URI_RE = /[a-z][\w+.-]*:\/\/\S+/gi;
11117
+ var EMAIL_RE = /\b[\w.+-]+@[\w.-]+\.\w+\b/g;
11118
+ var SECRET_RE = /\b(?=[A-Za-z0-9_+/=-]*[0-9])(?=[A-Za-z0-9_+/=-]*[A-Za-z])[A-Za-z0-9_+/=-]{16,}\b/g;
11119
+ var IP_RE = /\b\d{1,3}(?:\.\d{1,3}){3}\b/g;
11120
+ var WIN_PATH_RE = /[A-Za-z]:\\[^\s'"]+/g;
11121
+ var UNIX_PATH_RE = /(?:\/[\w.-]+){2,}/g;
11122
+ function redactForStorage(raw) {
11123
+ const redacted = raw.replace(URI_RE, " URL ").replace(EMAIL_RE, " EMAIL ").replace(SECRET_RE, " SECRET ").replace(IP_RE, " IP ").replace(WIN_PATH_RE, " PATH ").replace(UNIX_PATH_RE, " PATH ");
11124
+ return tokenize(redacted).join(" ");
11125
+ }
11126
+ function shouldCollect(c) {
11127
+ return c.category === "unknown" || c.margin <= STRENGTH.collectMaxMargin;
11128
+ }
11129
+ function tildeify(p) {
11130
+ const home = os5.homedir();
11131
+ return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
11132
+ }
11133
+ function collectUnclassified(classified, projectDir) {
11134
+ const rows = classified.filter(shouldCollect).map((c) => ({ text: redactForStorage(c.failure.error.message), category: c.category, margin: c.margin })).filter((r) => r.text.length > 0);
11135
+ if (rows.length === 0) return;
11136
+ const file = path5.join(projectDir, FILENAME);
11137
+ const firstTime = !fs4.existsSync(file);
11138
+ const byIdentity = /* @__PURE__ */ new Map();
11139
+ if (!firstTime) {
11140
+ for (const r of parseFeedbackRows(fs4.readFileSync(file, "utf8"))) {
11141
+ byIdentity.set(feedbackIdentity(r.category, r.text), r.raw);
11142
+ }
11143
+ }
11144
+ let added = false;
11145
+ for (const r of rows) {
11146
+ const id = feedbackIdentity(r.category, r.text);
11147
+ byIdentity.delete(id);
11148
+ byIdentity.set(id, `${r.category},${r.margin},${r.text}`);
11149
+ added = true;
11150
+ }
11151
+ if (!added) return;
11152
+ const all = [...byIdentity.values()].slice(-MAX_ROWS);
11153
+ fs4.mkdirSync(projectDir, { recursive: true });
11154
+ fs4.writeFileSync(file, `${FEEDBACK_HEADER}
11155
+ ${all.join("\n")}
11156
+ `, "utf8");
11157
+ if (firstTime) {
11158
+ logger.info(
11159
+ `Unrecognized failures collected locally at ${tildeify(file)} to improve offline classification. Local only - never sent anywhere. Disable: failureAnalysis.collectUnclassified=false`
11160
+ );
11161
+ }
11162
+ }
11163
+
11164
+ // src/utils/paths.ts
11165
+ init_esm_shims();
11166
+ import os6 from "os";
11167
+ import path6 from "path";
11168
+ import crypto from "crypto";
11169
+ function resolveProjectDir(options, cwd) {
11170
+ const projectKey = crypto.createHash("sha256").update(cwd + (options.outputFile ?? "")).digest("hex").slice(0, 8);
11171
+ return path6.join(os6.homedir(), ".reportforge", projectKey);
11172
+ }
11173
+
11174
+ // src/analysis/index.ts
11175
+ var DEFAULTS = { maxClusters: 10, minStrength: "weak", maxFailuresToAnalyse: 500 };
11176
+ var STRENGTH_RANK = Object.fromEntries(
11177
+ STRENGTH_LEVELS.map((level, i) => [level, i])
11178
+ );
11179
+ function analyseClassified(classified, totalFailures, opts = {}) {
11180
+ const maxClusters = opts.maxClusters ?? DEFAULTS.maxClusters;
11181
+ const minRank = STRENGTH_RANK[opts.minStrength ?? DEFAULTS.minStrength];
11182
+ const cap = opts.maxFailuresToAnalyse ?? DEFAULTS.maxFailuresToAnalyse;
11183
+ const clusters = clusterClassified(classified).filter((c) => STRENGTH_RANK[c.strength] >= minRank).sort((a, b) => b.count - a.count).slice(0, maxClusters);
11184
+ return {
11185
+ clusters,
11186
+ totalFailures,
11187
+ analysedCount: classified.length,
11188
+ truncated: totalFailures > cap,
11189
+ // dominantCategory is part of the AnalysisResult contract; reserved for
11190
+ // consumers (e.g. a future executive badge / notify summary). Not rendered
11191
+ // by the v1 partials.
11192
+ dominantCategory: dominantCategory(clusters),
11193
+ oneLiner: buildOneLiner(clusters)
11194
+ };
11195
+ }
11196
+ function templatesConsumeAnalysis(options) {
11197
+ if (options.templatePath && options.templatePath.length > 0) return true;
11198
+ const templates = Array.isArray(options.template) ? options.template : [options.template];
11199
+ return templates.some((t) => t === "detailed" || t === "executive");
11200
+ }
11201
+ function runFailureAnalysis(reportData, options, cwd) {
11202
+ const fa = options.failureAnalysis;
11203
+ if (reportData.stats.failed === 0 || !fa.enabled) return;
11204
+ const cap = fa.maxFailuresToAnalyse;
11205
+ const analysed = reportData.failures.slice(0, cap);
11206
+ const model = loadModel(fa.autoUpdateModel);
11207
+ const classified = classifyAll(analysed, model);
11208
+ if (templatesConsumeAnalysis(options)) {
11209
+ reportData.analysis = analyseClassified(classified, reportData.failures.length, fa);
11210
+ }
11211
+ if (fa.collectUnclassified) {
11212
+ collectUnclassified(classified, resolveProjectDir(options, cwd));
11213
+ }
11214
+ }
11215
+
10840
11216
  // src/collector/DataCollector.ts
10841
11217
  init_esm_shims();
10842
11218
 
11219
+ // src/utils/strip-ansi.ts
11220
+ init_esm_shims();
11221
+ var ANSI_PATTERN = new RegExp(
11222
+ [
11223
+ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
11224
+ "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"
11225
+ ].join("|"),
11226
+ "g"
11227
+ );
11228
+ function stripAnsi(input) {
11229
+ return input.replace(ANSI_PATTERN, "");
11230
+ }
11231
+
10843
11232
  // src/collector/SuiteWalker.ts
10844
11233
  init_esm_shims();
10845
11234
 
@@ -11074,8 +11463,8 @@ var DataCollector = class {
11074
11463
  testTitle: test.title,
11075
11464
  suitePath,
11076
11465
  error: {
11077
- message: error.message ?? String(error),
11078
- stack: error.stack
11466
+ message: stripAnsi(error.message ?? String(error)),
11467
+ stack: error.stack ? stripAnsi(error.stack) : void 0
11079
11468
  },
11080
11469
  screenshotPath: screenshotAttachment?.path,
11081
11470
  screenshotBuffer: screenshotAttachment?.body,
@@ -11397,8 +11786,8 @@ var ShardMerger = class {
11397
11786
  testTitle: spec.title,
11398
11787
  suitePath,
11399
11788
  error: {
11400
- message: error?.message ?? "Test failed",
11401
- stack: error?.stack
11789
+ message: stripAnsi(error?.message ?? "Test failed"),
11790
+ stack: error?.stack ? stripAnsi(error.stack) : void 0
11402
11791
  },
11403
11792
  screenshotPath: lastResult?.attachments.find(
11404
11793
  (a) => a.name.toLowerCase().includes("screenshot") && a.contentType.startsWith("image/")
@@ -11459,14 +11848,14 @@ var ShardMerger = class {
11459
11848
 
11460
11849
  // src/pdf/PdfGenerator.ts
11461
11850
  init_esm_shims();
11462
- import fs10 from "fs";
11463
- import path9 from "path";
11851
+ import fs12 from "fs";
11852
+ import path12 from "path";
11464
11853
 
11465
11854
  // src/templates/engine.ts
11466
11855
  init_esm_shims();
11467
11856
  var import_handlebars = __toESM(require_lib());
11468
- import fs3 from "fs";
11469
- import path4 from "path";
11857
+ import fs5 from "fs";
11858
+ import path7 from "path";
11470
11859
 
11471
11860
  // src/utils/duration.ts
11472
11861
  init_esm_shims();
@@ -11486,9 +11875,9 @@ var TemplateEngine = class {
11486
11875
  this.fontsBundleCss = "";
11487
11876
  this.customTemplateCache = /* @__PURE__ */ new Map();
11488
11877
  this.handlebars = import_handlebars.default.create();
11489
- this.templatesDir = path4.resolve(__dirname, "templates");
11490
- if (!fs3.existsSync(this.templatesDir)) {
11491
- this.templatesDir = path4.resolve(__dirname, "../templates");
11878
+ this.templatesDir = path7.resolve(__dirname, "templates");
11879
+ if (!fs5.existsSync(this.templatesDir)) {
11880
+ this.templatesDir = path7.resolve(__dirname, "../templates");
11492
11881
  }
11493
11882
  this.registerHelpers();
11494
11883
  this.registerPartials();
@@ -11505,21 +11894,21 @@ var TemplateEngine = class {
11505
11894
  this.chartjsInline = inline;
11506
11895
  }
11507
11896
  render(data, template) {
11508
- const layoutPath = path4.join(this.templatesDir, "layouts", `${template}.hbs`);
11509
- if (!fs3.existsSync(layoutPath)) {
11897
+ const layoutPath = path7.join(this.templatesDir, "layouts", `${template}.hbs`);
11898
+ if (!fs5.existsSync(layoutPath)) {
11510
11899
  throw new Error(`[reportforge] Template layout not found: ${layoutPath}`);
11511
11900
  }
11512
- const layoutSource = fs3.readFileSync(layoutPath, "utf8");
11901
+ const layoutSource = fs5.readFileSync(layoutPath, "utf8");
11513
11902
  const compiledLayout = this.handlebars.compile(layoutSource);
11514
11903
  const ctx = this.buildContext(data, template);
11515
11904
  return compiledLayout(ctx);
11516
11905
  }
11517
11906
  renderFromPath(data, filePath) {
11518
11907
  if (!this.customTemplateCache.has(filePath)) {
11519
- if (!fs3.existsSync(filePath)) {
11908
+ if (!fs5.existsSync(filePath)) {
11520
11909
  throw new Error(`[reportforge] Custom template not found: ${filePath}`);
11521
11910
  }
11522
- const source = fs3.readFileSync(filePath, "utf8");
11911
+ const source = fs5.readFileSync(filePath, "utf8");
11523
11912
  this.customTemplateCache.set(filePath, this.handlebars.compile(source));
11524
11913
  }
11525
11914
  const compiledFn = this.customTemplateCache.get(filePath);
@@ -11632,17 +12021,17 @@ var TemplateEngine = class {
11632
12021
  return tests.sort((a, b) => b.durationMs - a.durationMs).slice(0, 10);
11633
12022
  }
11634
12023
  readStyle(filename) {
11635
- const stylePath = path4.join(this.templatesDir, "styles", filename);
11636
- if (!fs3.existsSync(stylePath)) return "";
11637
- return fs3.readFileSync(stylePath, "utf8");
12024
+ const stylePath = path7.join(this.templatesDir, "styles", filename);
12025
+ if (!fs5.existsSync(stylePath)) return "";
12026
+ return fs5.readFileSync(stylePath, "utf8");
11638
12027
  }
11639
12028
  registerPartials() {
11640
- const partialsDir = path4.join(this.templatesDir, "partials");
11641
- if (!fs3.existsSync(partialsDir)) return;
11642
- for (const file of fs3.readdirSync(partialsDir)) {
12029
+ const partialsDir = path7.join(this.templatesDir, "partials");
12030
+ if (!fs5.existsSync(partialsDir)) return;
12031
+ for (const file of fs5.readdirSync(partialsDir)) {
11643
12032
  if (!file.endsWith(".hbs")) continue;
11644
- const name = path4.basename(file, ".hbs");
11645
- const src = fs3.readFileSync(path4.join(partialsDir, file), "utf8");
12033
+ const name = path7.basename(file, ".hbs");
12034
+ const src = fs5.readFileSync(path7.join(partialsDir, file), "utf8");
11646
12035
  this.handlebars.registerPartial(name, src);
11647
12036
  }
11648
12037
  }
@@ -11705,6 +12094,10 @@ var TemplateEngine = class {
11705
12094
  );
11706
12095
  this.handlebars.registerHelper("lt", (a, b) => a < b);
11707
12096
  this.handlebars.registerHelper("or", (a, b) => Boolean(a) || Boolean(b));
12097
+ this.handlebars.registerHelper("categoryLabel", (cat) => CATEGORY_LABEL[cat] ?? cat);
12098
+ this.handlebars.registerHelper("strengthLabel", (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : "");
12099
+ this.handlebars.registerHelper("pluralise", (n, one, many) => n === 1 ? one : many);
12100
+ this.handlebars.registerHelper("subtract", (a, b) => (a ?? 0) - (b ?? 0));
11708
12101
  this.handlebars.registerHelper("statusBadge", (status) => {
11709
12102
  const map = {
11710
12103
  passed: "badge--passed",
@@ -11726,7 +12119,7 @@ var TemplateEngine = class {
11726
12119
 
11727
12120
  // src/screenshots/ScreenshotEmbedder.ts
11728
12121
  init_esm_shims();
11729
- import fs4 from "fs";
12122
+ import fs6 from "fs";
11730
12123
  import sharp from "sharp";
11731
12124
  var ScreenshotEmbedder = class {
11732
12125
  async embedAll(failures, opts) {
@@ -11735,7 +12128,7 @@ var ScreenshotEmbedder = class {
11735
12128
  async embed(failure, opts) {
11736
12129
  if (!failure.screenshotPath && !failure.screenshotBuffer) return;
11737
12130
  try {
11738
- const raw = failure.screenshotBuffer ?? await fs4.promises.readFile(failure.screenshotPath);
12131
+ const raw = failure.screenshotBuffer ?? await fs6.promises.readFile(failure.screenshotPath);
11739
12132
  const { buffer, mime } = opts.reencode ? await this.recompress(raw, opts) : { buffer: raw, mime: this.detectMimeType(raw) };
11740
12133
  failure.screenshotBase64 = `data:${mime};base64,${buffer.toString("base64")}`;
11741
12134
  } catch (e) {
@@ -11747,7 +12140,7 @@ var ScreenshotEmbedder = class {
11747
12140
  }
11748
12141
  async embedLogoFile(logoPath) {
11749
12142
  try {
11750
- const buffer = await fs4.promises.readFile(logoPath);
12143
+ const buffer = await fs6.promises.readFile(logoPath);
11751
12144
  const ext = logoPath.split(".").pop()?.toLowerCase();
11752
12145
  let mime = "image/png";
11753
12146
  if (ext === "jpg" || ext === "jpeg") mime = "image/jpeg";
@@ -11792,7 +12185,7 @@ var ScreenshotEmbedder = class {
11792
12185
  // src/pdf/BrowserManager.ts
11793
12186
  init_esm_shims();
11794
12187
  import { execSync as execSync2 } from "child_process";
11795
- import fs5 from "fs";
12188
+ import fs7 from "fs";
11796
12189
  var BrowserManager = class {
11797
12190
  async launch(executablePath) {
11798
12191
  const puppeteer = __require("puppeteer-core");
@@ -11820,13 +12213,13 @@ var BrowserManager = class {
11820
12213
  }
11821
12214
  }
11822
12215
  resolveChromePath(explicit, puppeteer) {
11823
- if (explicit && fs5.existsSync(explicit)) return explicit;
12216
+ if (explicit && fs7.existsSync(explicit)) return explicit;
11824
12217
  const envPath = process.env.PUPPETEER_EXECUTABLE_PATH;
11825
- if (envPath && fs5.existsSync(envPath)) return envPath;
12218
+ if (envPath && fs7.existsSync(envPath)) return envPath;
11826
12219
  try {
11827
12220
  const chromeFinder = require_lib2();
11828
12221
  const found = chromeFinder();
11829
- if (found && fs5.existsSync(found)) return found;
12222
+ if (found && fs7.existsSync(found)) return found;
11830
12223
  } catch {
11831
12224
  }
11832
12225
  const linuxPaths = [
@@ -11837,19 +12230,19 @@ var BrowserManager = class {
11837
12230
  "/snap/bin/chromium"
11838
12231
  ];
11839
12232
  for (const p of linuxPaths) {
11840
- if (fs5.existsSync(p)) return p;
12233
+ if (fs7.existsSync(p)) return p;
11841
12234
  }
11842
12235
  try {
11843
12236
  const found = execSync2("which google-chrome chromium chromium-browser 2>/dev/null", {
11844
12237
  stdio: "pipe"
11845
12238
  }).toString().trim().split("\n")[0];
11846
- if (found && fs5.existsSync(found)) return found;
12239
+ if (found && fs7.existsSync(found)) return found;
11847
12240
  } catch {
11848
12241
  }
11849
12242
  if (puppeteer.executablePath) {
11850
12243
  try {
11851
12244
  const bundled = puppeteer.executablePath();
11852
- if (bundled && fs5.existsSync(bundled)) return bundled;
12245
+ if (bundled && fs7.existsSync(bundled)) return bundled;
11853
12246
  } catch {
11854
12247
  }
11855
12248
  }
@@ -11862,21 +12255,21 @@ var BrowserManager = class {
11862
12255
 
11863
12256
  // src/pdf/HtmlWriter.ts
11864
12257
  init_esm_shims();
11865
- import fs6 from "fs";
11866
- import os4 from "os";
11867
- import path5 from "path";
11868
- import crypto from "crypto";
12258
+ import fs8 from "fs";
12259
+ import os7 from "os";
12260
+ import path8 from "path";
12261
+ import crypto2 from "crypto";
11869
12262
  var HtmlWriter = class {
11870
12263
  async write(html) {
11871
- const tmpDir = os4.tmpdir();
11872
- const filename = `rf-report-${crypto.randomBytes(8).toString("hex")}.html`;
11873
- this.tempPath = path5.join(tmpDir, filename);
11874
- await fs6.promises.writeFile(this.tempPath, html, "utf8");
12264
+ const tmpDir = os7.tmpdir();
12265
+ const filename = `rf-report-${crypto2.randomBytes(8).toString("hex")}.html`;
12266
+ this.tempPath = path8.join(tmpDir, filename);
12267
+ await fs8.promises.writeFile(this.tempPath, html, "utf8");
11875
12268
  return this.tempPath;
11876
12269
  }
11877
12270
  async cleanup() {
11878
12271
  if (this.tempPath) {
11879
- await fs6.promises.unlink(this.tempPath).catch(() => {
12272
+ await fs8.promises.unlink(this.tempPath).catch(() => {
11880
12273
  });
11881
12274
  this.tempPath = void 0;
11882
12275
  }
@@ -11910,10 +12303,10 @@ var ChartRenderer = class {
11910
12303
  // src/pdf/PdfEncryptor.ts
11911
12304
  init_esm_shims();
11912
12305
  import { execFileSync } from "child_process";
11913
- import fs7 from "fs";
11914
- import path6 from "path";
11915
- import os5 from "os";
11916
- import crypto2 from "crypto";
12306
+ import fs9 from "fs";
12307
+ import path9 from "path";
12308
+ import os8 from "os";
12309
+ import crypto3 from "crypto";
11917
12310
  var PdfEncryptor = class {
11918
12311
  async encrypt(pdfPath, password) {
11919
12312
  if (!password) return;
@@ -11923,10 +12316,10 @@ var PdfEncryptor = class {
11923
12316
  );
11924
12317
  return;
11925
12318
  }
11926
- const tmpPath = path6.join(os5.tmpdir(), `rf-encrypted-${Date.now()}.pdf`);
11927
- const pwFile = path6.join(os5.tmpdir(), `rf-pw-${crypto2.randomBytes(8).toString("hex")}`);
12319
+ const tmpPath = path9.join(os8.tmpdir(), `rf-encrypted-${Date.now()}.pdf`);
12320
+ const pwFile = path9.join(os8.tmpdir(), `rf-pw-${crypto3.randomBytes(8).toString("hex")}`);
11928
12321
  try {
11929
- fs7.writeFileSync(pwFile, `${password}
12322
+ fs9.writeFileSync(pwFile, `${password}
11930
12323
  ${password}`, { mode: 384 });
11931
12324
  execFileSync("qpdf", [
11932
12325
  `--password-file=${pwFile}`,
@@ -11936,13 +12329,13 @@ ${password}`, { mode: 384 });
11936
12329
  pdfPath,
11937
12330
  tmpPath
11938
12331
  ], { stdio: "pipe" });
11939
- fs7.renameSync(tmpPath, pdfPath);
12332
+ fs9.renameSync(tmpPath, pdfPath);
11940
12333
  logger.info(`PDF encrypted with password.`);
11941
12334
  } catch (e) {
11942
12335
  logger.warn(`qpdf encryption failed: ${e.message}`);
11943
12336
  } finally {
11944
- if (fs7.existsSync(tmpPath)) fs7.unlinkSync(tmpPath);
11945
- fs7.rmSync(pwFile, { force: true });
12337
+ if (fs9.existsSync(tmpPath)) fs9.unlinkSync(tmpPath);
12338
+ fs9.rmSync(pwFile, { force: true });
11946
12339
  }
11947
12340
  }
11948
12341
  isQpdfAvailable() {
@@ -11957,8 +12350,8 @@ ${password}`, { mode: 384 });
11957
12350
 
11958
12351
  // src/filename/FilenameResolver.ts
11959
12352
  init_esm_shims();
11960
- import path7 from "path";
11961
- import fs8 from "fs";
12353
+ import path10 from "path";
12354
+ import fs10 from "fs";
11962
12355
  var MAX_BRANCH_LENGTH = 50;
11963
12356
  var FilenameResolver = class {
11964
12357
  /**
@@ -11975,8 +12368,8 @@ var FilenameResolver = class {
11975
12368
  const branch = this.sanitiseBranch(ctx.branch);
11976
12369
  const status = ctx.status;
11977
12370
  const resolved = template.replace(/\{date\}/g, date).replace(/\{branch\}/g, branch).replace(/\{status\}/g, status);
11978
- const absolute = path7.isAbsolute(resolved) ? resolved : path7.resolve(process.cwd(), resolved);
11979
- fs8.mkdirSync(path7.dirname(absolute), { recursive: true });
12371
+ const absolute = path10.isAbsolute(resolved) ? resolved : path10.resolve(process.cwd(), resolved);
12372
+ fs10.mkdirSync(path10.dirname(absolute), { recursive: true });
11980
12373
  return absolute;
11981
12374
  }
11982
12375
  /**
@@ -11988,7 +12381,7 @@ var FilenameResolver = class {
11988
12381
  * // → '/out/2026-report-executive.pdf'
11989
12382
  */
11990
12383
  injectTemplateSuffix(outputPath, suffix) {
11991
- const ext = path7.extname(outputPath);
12384
+ const ext = path10.extname(outputPath);
11992
12385
  const base = outputPath.slice(0, outputPath.length - ext.length);
11993
12386
  return `${base}-${suffix}${ext}`;
11994
12387
  }
@@ -12046,8 +12439,8 @@ var PRESETS = {
12046
12439
 
12047
12440
  // src/pdf/FailurePaginator.ts
12048
12441
  init_esm_shims();
12049
- import fs9 from "fs";
12050
- import path8 from "path";
12442
+ import fs11 from "fs";
12443
+ import path11 from "path";
12051
12444
  var FailurePaginator = class {
12052
12445
  async paginate(failures, maxInline, pdfOutputPath) {
12053
12446
  if (failures.length <= maxInline) {
@@ -12057,13 +12450,13 @@ var FailurePaginator = class {
12057
12450
  const overflow = failures.slice(maxInline);
12058
12451
  const sidecarPath = this.sidecarPathFor(pdfOutputPath);
12059
12452
  try {
12060
- await fs9.promises.writeFile(
12453
+ await fs11.promises.writeFile(
12061
12454
  sidecarPath,
12062
12455
  JSON.stringify(this.serialize(overflow), null, 2),
12063
12456
  "utf8"
12064
12457
  );
12065
12458
  logger.info(
12066
- `Emitted ${overflow.length} overflow failures \u2192 ${path8.basename(sidecarPath)}`
12459
+ `Emitted ${overflow.length} overflow failures \u2192 ${path11.basename(sidecarPath)}`
12067
12460
  );
12068
12461
  } catch (e) {
12069
12462
  logger.warn(`Could not write failure sidecar: ${e.message}`);
@@ -12072,9 +12465,9 @@ var FailurePaginator = class {
12072
12465
  return { inline, overflowCount: overflow.length, sidecarPath };
12073
12466
  }
12074
12467
  sidecarPathFor(pdfPath) {
12075
- const dir = path8.dirname(pdfPath);
12076
- const base = path8.basename(pdfPath, path8.extname(pdfPath));
12077
- return path8.join(dir, `${base}-failures.json`);
12468
+ const dir = path11.dirname(pdfPath);
12469
+ const base = path11.basename(pdfPath, path11.extname(pdfPath));
12470
+ return path11.join(dir, `${base}-failures.json`);
12078
12471
  }
12079
12472
  /**
12080
12473
  * Drops the inlined image bytes — a 150-entry overflow would otherwise
@@ -12152,7 +12545,7 @@ var PdfGenerator = class {
12152
12545
  const sources = this.resolveTemplateSources(options);
12153
12546
  const isMulti = sources.length > 1;
12154
12547
  for (const source of sources) {
12155
- if (source.kind === "custom" && !fs10.existsSync(source.absolutePath)) {
12548
+ if (source.kind === "custom" && !fs12.existsSync(source.absolutePath)) {
12156
12549
  throw new Error(
12157
12550
  `[reportforge] Custom template not found: ${source.absolutePath}`
12158
12551
  );
@@ -12183,14 +12576,14 @@ var PdfGenerator = class {
12183
12576
  await this.renderPdf(templateData, templateOptions, source, compression, outputPath, page);
12184
12577
  const capBytes = (options.maxFileSizeMb ?? 0) * 1024 * 1024;
12185
12578
  if (capBytes > 0) {
12186
- const sizeBytes = (await fs10.promises.stat(outputPath)).size;
12579
+ const sizeBytes = (await fs12.promises.stat(outputPath)).size;
12187
12580
  if (sizeBytes > capBytes) {
12188
12581
  const tighter = this.tightenForCap(compression, sizeBytes, capBytes);
12189
12582
  logger.warn(
12190
12583
  `PDF size ${fmtMb(sizeBytes)} exceeds cap ${fmtMb(capBytes)} \u2014 re-rendering (jpeg-q=${tighter.quality}, max-w=${tighter.maxWidth}px, inline-failures\u2264${tighter.maxInlineFailures}).`
12191
12584
  );
12192
12585
  await this.renderPdf(templateData, templateOptions, source, tighter, outputPath, page);
12193
- const finalSize = (await fs10.promises.stat(outputPath)).size;
12586
+ const finalSize = (await fs12.promises.stat(outputPath)).size;
12194
12587
  if (finalSize > capBytes) {
12195
12588
  logger.warn(
12196
12589
  `PDF still ${fmtMb(finalSize)} after retune. Consider lowering maxInlineFailures or splitting the report.`
@@ -12203,7 +12596,7 @@ var PdfGenerator = class {
12203
12596
  if (options.pdfPassword) {
12204
12597
  await this.pdfEncryptor.encrypt(outputPath, options.pdfPassword);
12205
12598
  }
12206
- const finalSizeMb = fmtMb((await fs10.promises.stat(outputPath)).size);
12599
+ const finalSizeMb = fmtMb((await fs12.promises.stat(outputPath)).size);
12207
12600
  logger.info(`PDF report generated: ${outputPath} (${finalSizeMb})`);
12208
12601
  outputPaths.push(outputPath);
12209
12602
  }
@@ -12216,8 +12609,8 @@ var PdfGenerator = class {
12216
12609
  if (options.templatePath && options.templatePath.length > 0) {
12217
12610
  return [...new Set(options.templatePath)].map((p) => ({
12218
12611
  kind: "custom",
12219
- absolutePath: path9.isAbsolute(p) ? p : path9.resolve(process.cwd(), p),
12220
- label: path9.basename(p, ".hbs")
12612
+ absolutePath: path12.isAbsolute(p) ? p : path12.resolve(process.cwd(), p),
12613
+ label: path12.basename(p, ".hbs")
12221
12614
  }));
12222
12615
  }
12223
12616
  const rawIds = Array.isArray(options.template) ? options.template : [options.template];
@@ -12267,7 +12660,7 @@ var PdfGenerator = class {
12267
12660
  ...data,
12268
12661
  failures: paginated.inline,
12269
12662
  overflowFailureCount: paginated.overflowCount,
12270
- overflowSidecarName: paginated.sidecarPath ? path9.basename(paginated.sidecarPath) : void 0
12663
+ overflowSidecarName: paginated.sidecarPath ? path12.basename(paginated.sidecarPath) : void 0
12271
12664
  };
12272
12665
  for (const f of renderData.failures) f.screenshotBase64 = void 0;
12273
12666
  if (options.includeScreenshots !== false) {
@@ -12287,7 +12680,7 @@ var PdfGenerator = class {
12287
12680
  await page.goto(fileUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
12288
12681
  const showCharts = source.kind === "builtin" ? source.id !== "minimal" : true;
12289
12682
  await this.chartRenderer.waitForCharts(page, showCharts);
12290
- logger.info(`Generating PDF \u2192 ${path9.relative(process.cwd(), outputPath)}`);
12683
+ logger.info(`Generating PDF \u2192 ${path12.relative(process.cwd(), outputPath)}`);
12291
12684
  await page.pdf({
12292
12685
  path: outputPath,
12293
12686
  format: "A4",
@@ -12304,21 +12697,19 @@ function fmtMb(bytes) {
12304
12697
  }
12305
12698
 
12306
12699
  // src/reporter.ts
12307
- import path11 from "path";
12308
- import crypto3 from "crypto";
12309
- import os6 from "os";
12700
+ import path14 from "path";
12310
12701
 
12311
12702
  // src/history/HistoryManager.ts
12312
12703
  init_esm_shims();
12313
- import fs11 from "fs";
12314
- import path10 from "path";
12704
+ import fs13 from "fs";
12705
+ import path13 from "path";
12315
12706
  var HistoryManager = class {
12316
12707
  constructor(filePath) {
12317
12708
  this.filePath = filePath;
12318
12709
  }
12319
12710
  read() {
12320
12711
  try {
12321
- const raw = fs11.readFileSync(this.filePath, "utf8");
12712
+ const raw = fs13.readFileSync(this.filePath, "utf8");
12322
12713
  const parsed = JSON.parse(raw);
12323
12714
  if (parsed.version !== 1) {
12324
12715
  logger.warn(`history.version-mismatch: path=${this.filePath} version=${String(parsed.version)}`);
@@ -12332,12 +12723,12 @@ var HistoryManager = class {
12332
12723
  }
12333
12724
  append(entry, maxSize) {
12334
12725
  try {
12335
- fs11.mkdirSync(path10.dirname(this.filePath), { recursive: true });
12726
+ fs13.mkdirSync(path13.dirname(this.filePath), { recursive: true });
12336
12727
  const next = {
12337
12728
  version: 1,
12338
12729
  entries: [entry, ...this.read()].slice(0, maxSize)
12339
12730
  };
12340
- fs11.writeFileSync(this.filePath, JSON.stringify(next, null, 2), { mode: 420 });
12731
+ fs13.writeFileSync(this.filePath, JSON.stringify(next, null, 2), { mode: 420 });
12341
12732
  return next.entries;
12342
12733
  } catch (err) {
12343
12734
  logger.warn(`history.write-failed: path=${this.filePath} error=${err.message}`);
@@ -12678,11 +13069,13 @@ var PdfReporter = class {
12678
13069
  this.dataCollector = new DataCollector();
12679
13070
  this.pdfGenerator = new PdfGenerator();
12680
13071
  this.options = parseOptions(rawOptions);
12681
- const version = true ? "0.3.1" : "0.x";
13072
+ const version = true ? "0.4.1" : "0.x";
12682
13073
  logger.info(`@reportforge/playwright-pdf v${version} initialised`);
12683
13074
  this.licenseClient = new LicenseClient({
12684
13075
  licenseKey: this.options.licenseKey,
12685
- serverUrl: this.options.serverUrl
13076
+ serverUrl: this.options.serverUrl,
13077
+ autoUpdateModel: this.options.failureAnalysis.autoUpdateModel,
13078
+ currentModelVersion: MODEL.version
12686
13079
  });
12687
13080
  }
12688
13081
  onBegin(config, suite) {
@@ -12704,6 +13097,7 @@ var PdfReporter = class {
12704
13097
  }
12705
13098
  }
12706
13099
  const reportData = this._buildReportData(collected, licenseInfo);
13100
+ runFailureAnalysis(reportData, this.options, this.cwd);
12707
13101
  let pdfPaths = [];
12708
13102
  try {
12709
13103
  pdfPaths = await this.pdfGenerator.generateAll(reportData, this.options, licenseInfo);
@@ -12810,7 +13204,7 @@ var PdfReporter = class {
12810
13204
  }
12811
13205
  resolveProjectName() {
12812
13206
  try {
12813
- const pkgPath = path11.resolve(process.cwd(), "package.json");
13207
+ const pkgPath = path14.resolve(process.cwd(), "package.json");
12814
13208
  const pkg = __require(pkgPath);
12815
13209
  return pkg.name ?? "Playwright Tests";
12816
13210
  } catch {
@@ -12860,10 +13254,9 @@ function deriveVerdict(stats) {
12860
13254
  }
12861
13255
  function resolveHistoryPath(options, cwd) {
12862
13256
  if (options.historyFile) {
12863
- return path11.isAbsolute(options.historyFile) ? options.historyFile : path11.resolve(cwd, options.historyFile);
13257
+ return path14.isAbsolute(options.historyFile) ? options.historyFile : path14.resolve(cwd, options.historyFile);
12864
13258
  }
12865
- const projectKey = crypto3.createHash("sha256").update(cwd + (options.outputFile ?? "")).digest("hex").slice(0, 8);
12866
- return path11.join(os6.homedir(), ".reportforge", projectKey, "history.json");
13259
+ return path14.join(resolveProjectDir(options, cwd), "history.json");
12867
13260
  }
12868
13261
  function flattenSuiteNodes(suites) {
12869
13262
  return suites.flatMap((s) => [s, ...flattenSuiteNodes(s.suites)]);