@reportforge/playwright-pdf 0.19.1 → 0.20.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/cli/index.js CHANGED
@@ -1588,13 +1588,13 @@ var require_ast = __commonJS({
1588
1588
  helperExpression: function helperExpression(node) {
1589
1589
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
1590
1590
  },
1591
- scopedId: function scopedId(path12) {
1592
- return /^\.|this\b/.test(path12.original);
1591
+ scopedId: function scopedId(path14) {
1592
+ return /^\.|this\b/.test(path14.original);
1593
1593
  },
1594
1594
  // an ID is simple if it only has one part, and that part is not
1595
1595
  // `..` or `this`.
1596
- simpleId: function simpleId(path12) {
1597
- return path12.parts.length === 1 && !AST.helpers.scopedId(path12) && !path12.depth;
1596
+ simpleId: function simpleId(path14) {
1597
+ return path14.parts.length === 1 && !AST.helpers.scopedId(path14) && !path14.depth;
1598
1598
  }
1599
1599
  }
1600
1600
  };
@@ -2668,12 +2668,12 @@ var require_helpers2 = __commonJS({
2668
2668
  loc
2669
2669
  };
2670
2670
  }
2671
- function prepareMustache(path12, params, hash, open, strip, locInfo) {
2671
+ function prepareMustache(path14, params, hash, open, strip, locInfo) {
2672
2672
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
2673
2673
  var decorator = /\*/.test(open);
2674
2674
  return {
2675
2675
  type: decorator ? "Decorator" : "MustacheStatement",
2676
- path: path12,
2676
+ path: path14,
2677
2677
  params,
2678
2678
  hash,
2679
2679
  escaped,
@@ -2993,9 +2993,9 @@ var require_compiler = __commonJS({
2993
2993
  },
2994
2994
  DecoratorBlock: function DecoratorBlock(decorator) {
2995
2995
  var program = decorator.program && this.compileProgram(decorator.program);
2996
- var params = this.setupFullMustacheParams(decorator, program, void 0), path12 = decorator.path;
2996
+ var params = this.setupFullMustacheParams(decorator, program, void 0), path14 = decorator.path;
2997
2997
  this.useDecorators = true;
2998
- this.opcode("registerDecorator", params.length, path12.original);
2998
+ this.opcode("registerDecorator", params.length, path14.original);
2999
2999
  },
3000
3000
  PartialStatement: function PartialStatement(partial) {
3001
3001
  this.usePartial = true;
@@ -3059,46 +3059,46 @@ var require_compiler = __commonJS({
3059
3059
  }
3060
3060
  },
3061
3061
  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
3062
- var path12 = sexpr.path, name = path12.parts[0], isBlock = program != null || inverse != null;
3063
- this.opcode("getContext", path12.depth);
3062
+ var path14 = sexpr.path, name = path14.parts[0], isBlock = program != null || inverse != null;
3063
+ this.opcode("getContext", path14.depth);
3064
3064
  this.opcode("pushProgram", program);
3065
3065
  this.opcode("pushProgram", inverse);
3066
- path12.strict = true;
3067
- this.accept(path12);
3066
+ path14.strict = true;
3067
+ this.accept(path14);
3068
3068
  this.opcode("invokeAmbiguous", name, isBlock);
3069
3069
  },
3070
3070
  simpleSexpr: function simpleSexpr(sexpr) {
3071
- var path12 = sexpr.path;
3072
- path12.strict = true;
3073
- this.accept(path12);
3071
+ var path14 = sexpr.path;
3072
+ path14.strict = true;
3073
+ this.accept(path14);
3074
3074
  this.opcode("resolvePossibleLambda");
3075
3075
  },
3076
3076
  helperSexpr: function helperSexpr(sexpr, program, inverse) {
3077
- var params = this.setupFullMustacheParams(sexpr, program, inverse), path12 = sexpr.path, name = path12.parts[0];
3077
+ var params = this.setupFullMustacheParams(sexpr, program, inverse), path14 = sexpr.path, name = path14.parts[0];
3078
3078
  if (this.options.knownHelpers[name]) {
3079
3079
  this.opcode("invokeKnownHelper", params.length, name);
3080
3080
  } else if (this.options.knownHelpersOnly) {
3081
3081
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
3082
3082
  } else {
3083
- path12.strict = true;
3084
- path12.falsy = true;
3085
- this.accept(path12);
3086
- this.opcode("invokeHelper", params.length, path12.original, _ast2["default"].helpers.simpleId(path12));
3083
+ path14.strict = true;
3084
+ path14.falsy = true;
3085
+ this.accept(path14);
3086
+ this.opcode("invokeHelper", params.length, path14.original, _ast2["default"].helpers.simpleId(path14));
3087
3087
  }
3088
3088
  },
3089
- PathExpression: function PathExpression(path12) {
3090
- this.addDepth(path12.depth);
3091
- this.opcode("getContext", path12.depth);
3092
- var name = path12.parts[0], scoped = _ast2["default"].helpers.scopedId(path12), blockParamId = !path12.depth && !scoped && this.blockParamIndex(name);
3089
+ PathExpression: function PathExpression(path14) {
3090
+ this.addDepth(path14.depth);
3091
+ this.opcode("getContext", path14.depth);
3092
+ var name = path14.parts[0], scoped = _ast2["default"].helpers.scopedId(path14), blockParamId = !path14.depth && !scoped && this.blockParamIndex(name);
3093
3093
  if (blockParamId) {
3094
- this.opcode("lookupBlockParam", blockParamId, path12.parts);
3094
+ this.opcode("lookupBlockParam", blockParamId, path14.parts);
3095
3095
  } else if (!name) {
3096
3096
  this.opcode("pushContext");
3097
- } else if (path12.data) {
3097
+ } else if (path14.data) {
3098
3098
  this.options.data = true;
3099
- this.opcode("lookupData", path12.depth, path12.parts, path12.strict);
3099
+ this.opcode("lookupData", path14.depth, path14.parts, path14.strict);
3100
3100
  } else {
3101
- this.opcode("lookupOnContext", path12.parts, path12.falsy, path12.strict, scoped);
3101
+ this.opcode("lookupOnContext", path14.parts, path14.falsy, path14.strict, scoped);
3102
3102
  }
3103
3103
  },
3104
3104
  StringLiteral: function StringLiteral(string) {
@@ -3454,16 +3454,16 @@ var require_util = __commonJS({
3454
3454
  }
3455
3455
  exports2.urlGenerate = urlGenerate;
3456
3456
  function normalize(aPath) {
3457
- var path12 = aPath;
3457
+ var path14 = aPath;
3458
3458
  var url = urlParse(aPath);
3459
3459
  if (url) {
3460
3460
  if (!url.path) {
3461
3461
  return aPath;
3462
3462
  }
3463
- path12 = url.path;
3463
+ path14 = url.path;
3464
3464
  }
3465
- var isAbsolute = exports2.isAbsolute(path12);
3466
- var parts = path12.split(/\/+/);
3465
+ var isAbsolute = exports2.isAbsolute(path14);
3466
+ var parts = path14.split(/\/+/);
3467
3467
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
3468
3468
  part = parts[i];
3469
3469
  if (part === ".") {
@@ -3480,15 +3480,15 @@ var require_util = __commonJS({
3480
3480
  }
3481
3481
  }
3482
3482
  }
3483
- path12 = parts.join("/");
3484
- if (path12 === "") {
3485
- path12 = isAbsolute ? "/" : ".";
3483
+ path14 = parts.join("/");
3484
+ if (path14 === "") {
3485
+ path14 = isAbsolute ? "/" : ".";
3486
3486
  }
3487
3487
  if (url) {
3488
- url.path = path12;
3488
+ url.path = path14;
3489
3489
  return urlGenerate(url);
3490
3490
  }
3491
- return path12;
3491
+ return path14;
3492
3492
  }
3493
3493
  exports2.normalize = normalize;
3494
3494
  function join(aRoot, aPath) {
@@ -6291,8 +6291,8 @@ var require_printer = __commonJS({
6291
6291
  return this.accept(sexpr.path) + " " + params + hash;
6292
6292
  };
6293
6293
  PrintVisitor.prototype.PathExpression = function(id) {
6294
- var path12 = id.parts.join("/");
6295
- return (id.data ? "@" : "") + "PATH:" + path12;
6294
+ var path14 = id.parts.join("/");
6295
+ return (id.data ? "@" : "") + "PATH:" + path14;
6296
6296
  };
6297
6297
  PrintVisitor.prototype.StringLiteral = function(string) {
6298
6298
  return '"' + string.value + '"';
@@ -6333,8 +6333,8 @@ var require_lib = __commonJS({
6333
6333
  handlebars.print = printer.print;
6334
6334
  module2.exports = handlebars;
6335
6335
  function extension(module3, filename) {
6336
- var fs12 = require("fs");
6337
- var templateString = fs12.readFileSync(filename, "utf8");
6336
+ var fs14 = require("fs");
6337
+ var templateString = fs14.readFileSync(filename, "utf8");
6338
6338
  module3.exports = handlebars.compile(templateString);
6339
6339
  }
6340
6340
  if (typeof require !== "undefined" && require.extensions) {
@@ -6854,8 +6854,8 @@ var init_engine = __esm({
6854
6854
  };
6855
6855
  return map[v] ?? (typeof v === "string" ? v.toUpperCase() : v);
6856
6856
  });
6857
- this.handlebars.registerHelper("coverageBand", (pct) => {
6858
- const n = Number(pct);
6857
+ this.handlebars.registerHelper("coverageBand", (pct2) => {
6858
+ const n = Number(pct2);
6859
6859
  return n >= 80 ? "high" : n >= 50 ? "mid" : "low";
6860
6860
  });
6861
6861
  this.handlebars.registerHelper("addOne", (n) => (n ?? 0) + 1);
@@ -7005,7 +7005,7 @@ var require_util2 = __commonJS({
7005
7005
  "node_modules/chrome-finder/lib/util.js"(exports2, module2) {
7006
7006
  "use strict";
7007
7007
  init_cjs_shims();
7008
- var fs12 = require("fs");
7008
+ var fs14 = require("fs");
7009
7009
  var newLineRegex = /\r?\n/;
7010
7010
  function sort(installations, priorities) {
7011
7011
  const defaultPriority = 10;
@@ -7023,7 +7023,7 @@ var require_util2 = __commonJS({
7023
7023
  return false;
7024
7024
  }
7025
7025
  try {
7026
- fs12.accessSync(file);
7026
+ fs14.accessSync(file);
7027
7027
  return true;
7028
7028
  } catch (e) {
7029
7029
  return false;
@@ -7034,7 +7034,7 @@ var require_util2 = __commonJS({
7034
7034
  return false;
7035
7035
  }
7036
7036
  try {
7037
- var stat = fs12.statSync(file);
7037
+ var stat = fs14.statSync(file);
7038
7038
  return stat && typeof stat.isFile === "function" && stat.isFile();
7039
7039
  } catch (e) {
7040
7040
  return false;
@@ -7054,7 +7054,7 @@ var require_win32 = __commonJS({
7054
7054
  "node_modules/chrome-finder/lib/win32.js"(exports2, module2) {
7055
7055
  "use strict";
7056
7056
  init_cjs_shims();
7057
- var path12 = require("path");
7057
+ var path14 = require("path");
7058
7058
  var { canAccess } = require_util2();
7059
7059
  function win32() {
7060
7060
  const installations = [];
@@ -7068,7 +7068,7 @@ var require_win32 = __commonJS({
7068
7068
  const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]];
7069
7069
  prefixes.forEach((prefix) => suffixes.forEach((suffix) => {
7070
7070
  if (prefix) {
7071
- const chromePath = path12.join(prefix, suffix);
7071
+ const chromePath = path14.join(prefix, suffix);
7072
7072
  if (canAccess(chromePath)) {
7073
7073
  installations.push(chromePath);
7074
7074
  }
@@ -7086,7 +7086,7 @@ var require_darwin = __commonJS({
7086
7086
  "use strict";
7087
7087
  init_cjs_shims();
7088
7088
  var { execSync: execSync2 } = require("child_process");
7089
- var path12 = require("path");
7089
+ var path14 = require("path");
7090
7090
  var { canAccess, newLineRegex, sort } = require_util2();
7091
7091
  function darwin() {
7092
7092
  const suffixes = ["/Contents/MacOS/Google Chrome Canary", "/Contents/MacOS/Google Chrome", "/Contents/MacOS/Chromium"];
@@ -7096,7 +7096,7 @@ var require_darwin = __commonJS({
7096
7096
  ${LSREGISTER} -dump | grep -E -i -o '/.+(google chrome( canary)?|chromium)\\.app(\\s|$)' | grep -E -v 'Caches|TimeMachine|Temporary|/Volumes|\\.Trash'
7097
7097
  `).toString().split(newLineRegex).forEach((inst) => {
7098
7098
  suffixes.forEach((suffix) => {
7099
- const execPath = path12.join(inst.trim(), suffix);
7099
+ const execPath = path14.join(inst.trim(), suffix);
7100
7100
  if (canAccess(execPath)) {
7101
7101
  installations.push(execPath);
7102
7102
  }
@@ -7125,8 +7125,8 @@ var require_linux = __commonJS({
7125
7125
  "use strict";
7126
7126
  init_cjs_shims();
7127
7127
  var { execSync: execSync2, execFileSync: execFileSync2 } = require("child_process");
7128
- var path12 = require("path");
7129
- var fs12 = require("fs");
7128
+ var path14 = require("path");
7129
+ var fs14 = require("fs");
7130
7130
  var { canAccess, sort, isExecutable, newLineRegex } = require_util2();
7131
7131
  function findChromeExecutablesForLinuxDesktop(folder) {
7132
7132
  const argumentsRegex = /(^[^ ]+).*/;
@@ -7143,7 +7143,7 @@ var require_linux = __commonJS({
7143
7143
  function linux() {
7144
7144
  let installations = [];
7145
7145
  const desktopInstallationFolders = [
7146
- path12.join(require("os").homedir(), ".local/share/applications/"),
7146
+ path14.join(require("os").homedir(), ".local/share/applications/"),
7147
7147
  "/usr/share/applications/"
7148
7148
  ];
7149
7149
  desktopInstallationFolders.forEach((folder) => {
@@ -7169,7 +7169,7 @@ var require_linux = __commonJS({
7169
7169
  ].map((possiblePath) => {
7170
7170
  try {
7171
7171
  const chromePathToTest = possiblePath + "/" + executable;
7172
- if (fs12.existsSync(chromePathToTest) && canAccess(chromePathToTest) && isExecutable(chromePathToTest)) {
7172
+ if (fs14.existsSync(chromePathToTest) && canAccess(chromePathToTest) && isExecutable(chromePathToTest)) {
7173
7173
  installations.push(chromePathToTest);
7174
7174
  return chromePathToTest;
7175
7175
  }
@@ -8302,8 +8302,8 @@ var init_parseUtil = __esm({
8302
8302
  init_errors();
8303
8303
  init_en();
8304
8304
  makeIssue = (params) => {
8305
- const { data, path: path12, errorMaps, issueData } = params;
8306
- const fullPath = [...path12, ...issueData.path || []];
8305
+ const { data, path: path14, errorMaps, issueData } = params;
8306
+ const fullPath = [...path14, ...issueData.path || []];
8307
8307
  const fullIssue = {
8308
8308
  ...issueData,
8309
8309
  path: fullPath
@@ -8617,11 +8617,11 @@ var init_types = __esm({
8617
8617
  init_parseUtil();
8618
8618
  init_util();
8619
8619
  ParseInputLazyPath = class {
8620
- constructor(parent, value, path12, key) {
8620
+ constructor(parent, value, path14, key) {
8621
8621
  this._cachedPath = [];
8622
8622
  this.parent = parent;
8623
8623
  this.data = value;
8624
- this._path = path12;
8624
+ this._path = path14;
8625
8625
  this._key = key;
8626
8626
  }
8627
8627
  get path() {
@@ -12054,9 +12054,17 @@ var init_schema = __esm({
12054
12054
  // Qualitative floor (not a numeric % — consistent with the match-strength design).
12055
12055
  minStrength: external_exports.enum(["weak", "moderate", "strong"]).default("weak"),
12056
12056
  maxFailuresToAnalyse: external_exports.number().int().min(1).default(500),
12057
- // Consumed by the Plan 3 feedback collector; inert until then.
12058
12057
  collectUnclassified: external_exports.boolean().default(true),
12059
- autoUpdateModel: external_exports.boolean().default(true)
12058
+ // What the feedback collector stores (while collectUnclassified is on):
12059
+ // 'blind-spots' = unknown/low-confidence only; 'all' = every model-scored
12060
+ // failure, so wrong-but-confident verdicts can be corrected by labeling.
12061
+ collectScope: external_exports.enum(["blind-spots", "all"]).default("blind-spots"),
12062
+ autoUpdateModel: external_exports.boolean().default(true),
12063
+ // Use the on-device trained model (when present and its evaluation gate passed).
12064
+ localModel: external_exports.boolean().default(true),
12065
+ // Override the local model file location (default ~/.reportforge/model-local.json).
12066
+ // Point at a repo-committed file to share a team-trained model with ephemeral CI.
12067
+ localModelPath: external_exports.string().optional()
12060
12068
  }).default({});
12061
12069
  captureConfigSchema = external_exports.object({
12062
12070
  steps: external_exports.boolean().default(false),
@@ -12153,7 +12161,57 @@ var init_model_bundle = __esm({
12153
12161
  "src/analysis/model-bundle.ts"() {
12154
12162
  "use strict";
12155
12163
  init_cjs_shims();
12156
- 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 };
12164
+ MODEL = { "alpha": 1, "labels": ["assertion", "env-config", "flaky-by-retry", "locator-not-found", "navigation", "network", "timeout"], "logLikelihoods": { "assertion": { "13": -6.424058322604399, "16": -6.424058322604399, "17": -6.424058322604399, "20": -6.424058322604399, "21": -6.018593214496234, "22": -6.424058322604399, "23": -6.424058322604399, "25": -6.424058322604399, "30": -6.424058322604399, "41": -6.424058322604399, "43": -6.424058322604399, "61": -6.424058322604399, "00": -6.018593214496234, "03": -6.424058322604399, "12t07": -6.424058322604399, "6s": -6.424058322604399, "NUM": -4.283992159108128, "URL": -6.424058322604399, "actual": -6.018593214496234, "am": -6.424058322604399, "assertionerror": -6.424058322604399, "assertionname": -6.424058322604399, "async": -6.424058322604399, "at": -5.507767590730244, "await": -5.32544603393629, "basic": -5.507767590730244, "be": -5.730911142044453, "below": -6.424058322604399, "button": -6.424058322604399, "call": -5.507767590730244, "cart": -6.424058322604399, "checkout": -6.424058322604399, "com": -6.424058322604399, "const": -6.424058322604399, "count": -5.730911142044453, "dashboard": -6.424058322604399, "deep": -6.424058322604399, "downloads": -6.018593214496234, "equal": -6.424058322604399, "equality": -6.424058322604399, "error": -5.32544603393629, "example": -5.171295354109031, "expect": -4.283992159108128, "expected": -3.9817112872351945, "failed": -6.018593214496234, "false": -6.424058322604399, "field": -6.424058322604399, "for": -5.507767590730244, "getbytext": -6.018593214496234, "getting": -6.424058322604399, "ggorlen": -6.424058322604399, "got": -6.424058322604399, "goto": -6.424058322604399, "id": -6.018593214496234, "in": -6.018593214496234, "inner": -6.018593214496234, "internal": -6.018593214496234, "isnot": -6.018593214496234, "john": -6.424058322604399, "js": -5.32544603393629, "lib": -6.424058322604399, "locator": -4.478148173549085, "log": -5.507767590730244, "logged": -6.018593214496234, "login": -6.424058322604399, "matcherhint": -6.424058322604399, "matcherresult": -5.507767590730244, "modules": -6.424058322604399, "must": -6.424058322604399, "my": -6.424058322604399, "name": -6.018593214496234, "navbar": -5.507767590730244, "node": -5.507767590730244, "not": -6.424058322604399, "npx": -6.424058322604399, "page": -5.171295354109031, "pattern": -6.424058322604399, "playwright": -5.32544603393629, "printexpected": -6.018593214496234, "printreceived": -6.018593214496234, "process": -6.424058322604399, "processimmediate": -6.424058322604399, "queues": -6.424058322604399, "received": -4.22683374526818, "reply": -6.424058322604399, "return": -6.424058322604399, "runnextticks": -6.424058322604399, "running": -6.424058322604399, "runtestwithbeforehooks": -6.424058322604399, "selector": -6.424058322604399, "should": -6.018593214496234, "spec": -5.32544603393629, "string": -5.037763961484508, "task": -6.424058322604399, "test": -4.814620410170298, "tests": -5.507767590730244, "text": -6.424058322604399, "this": -5.171295354109031, "timeout": -5.730911142044453, "timers": -6.424058322604399, "title": -5.507767590730244, "to": -6.424058322604399, "tobe": -6.424058322604399, "tobetruthy": -6.424058322604399, "tobevisible": -6.018593214496234, "toequal": -6.424058322604399, "tohavecount": -6.424058322604399, "tohavetext": -4.919980925828125, "tohavetitle": -6.424058322604399, "tohavevalue": -6.424058322604399, "truthy": -6.424058322604399, "ts": -6.424058322604399, "undefined": -6.018593214496234, "users": -6.018593214496234, "using": -6.424058322604399, "utils": -5.32544603393629, "value": -6.424058322604399, "vu": -6.018593214496234, "waiting": -5.507767590730244, "with": -5.730911142044453, "worker": -6.424058322604399, "workerrunner": -6.018593214496234 }, "env-config": { "16": -6.687108607866515, "44": -6.687108607866515, "64": -6.687108607866515, "66": -6.687108607866515, "70": -5.77081787599236, "00": -6.28164349975835, "05": -6.687108607866515, "16t15": -6.687108607866515, "51z": -6.687108607866515, "8hauptrolle": -6.687108607866515, "NUM": -5.18303121109024, "activity": -6.687108607866515, "add": -6.687108607866515, "additional": -6.687108607866515, "aimed": -6.687108607866515, "alderoberge": -6.687108607866515, "alejo": -6.687108607866515, "also": -5.588496319198405, "and": -5.588496319198405, "any": -6.28164349975835, "anyone": -6.28164349975835, "api": -6.687108607866515, "assist": -6.687108607866515, "at": -5.434345639371147, "available": -6.28164349975835, "awb": -6.28164349975835, "back": -6.687108607866515, "base": -6.687108607866515, "baseurl": -6.687108607866515, "be": -6.28164349975835, "believe": -6.687108607866515, "binary": -6.687108607866515, "box": -6.28164349975835, "bridge": -6.687108607866515, "browser": -6.687108607866515, "browsername": -5.588496319198405, "browsers": -5.18303121109024, "browsertype": -4.9823605156280895, "but": -6.28164349975835, "by": -6.687108607866515, "cache": -5.77081787599236, "can": -6.28164349975835, "cannot": -6.687108607866515, "channel": -5.077670695432414, "cheron": -6.687108607866515, "chrome": -5.18303121109024, "chromium": -4.9823605156280895, "ci": -6.687108607866515, "cmu": -4.9823605156280895, "cmulex": -6.687108607866515, "command": -5.993961427306569, "commented": -6.687108607866515, "config": -5.993961427306569, "default": -6.687108607866515, "dependencies": -5.588496319198405, "deps": -5.993961427306569, "despite": -6.687108607866515, "difference": -6.687108607866515, "different": -6.687108607866515, "directory": -6.28164349975835, "displays": -6.687108607866515, "distros": -5.993961427306569, "do": -6.28164349975835, "docker": -6.687108607866515, "doesn": -5.434345639371147, "dotenv": -6.687108607866515, "download": -6.28164349975835, "edit": -6.687108607866515, "effort": -6.687108607866515, "elementary": -6.687108607866515, "enoent": -6.687108607866515, "env": -6.28164349975835, "environment": -6.28164349975835, "error": -4.741198458811201, "example": -6.687108607866515, "exec": -6.687108607866515, "executable": -5.77081787599236, "exist": -5.77081787599236, "existent": -6.687108607866515, "explain": -6.28164349975835, "eyes": -6.687108607866515, "failed": -6.687108607866515, "fak3": -6.687108607866515, "falling": -6.687108607866515, "file": -6.28164349975835, "files": -6.687108607866515, "find": -6.687108607866515, "firefox": -6.28164349975835, "fix": -6.687108607866515, "fixing": -6.687108607866515, "following": -6.28164349975835, "for": -5.588496319198405, "from": -6.687108607866515, "gnu": -6.687108607866515, "goes": -6.687108607866515, "goto": -6.687108607866515, "grapheme": -6.28164349975835, "guess": -6.28164349975835, "has": -6.28164349975835, "have": -6.28164349975835, "having": -6.28164349975835, "home": -6.687108607866515, "hopefully": -6.687108607866515, "host": -5.77081787599236, "if": -6.28164349975835, "image": -6.687108607866515, "in": -6.28164349975835, "incompatible": -6.687108607866515, "indic": -6.28164349975835, "information": -6.687108607866515, "insights": -6.687108607866515, "install": -5.588496319198405, "installed": -5.993961427306569, "instead": -6.687108607866515, "into": -6.687108607866515, "invalid": -6.687108607866515, "is": -5.077670695432414, "isn": -6.687108607866515, "issue": -5.588496319198405, "it": -4.9823605156280895, "js": -6.687108607866515, "json": -6.28164349975835, "just": -5.588496319198405, "kal": -6.687108607866515, "kal16": -6.687108607866515, "keep": -6.687108607866515, "key": -6.687108607866515, "kunukn": -6.687108607866515, "lang": -6.28164349975835, "launch": -4.9823605156280895, "leo": -6.687108607866515, "lex": -6.28164349975835, "lib": -6.687108607866515, "libatk": -6.28164349975835, "libatspi": -6.687108607866515, "libcups": -6.687108607866515, "libdbus": -6.687108607866515, "libdrm": -6.687108607866515, "libexpat": -6.687108607866515, "libflite": -4.741198458811201, "libgio": -6.687108607866515, "libglib": -6.687108607866515, "libgobject": -6.687108607866515, "libgstcodecparsers": -6.687108607866515, "libicu70": -6.687108607866515, "libicudata": -6.687108607866515, "libicui18n": -6.687108607866515, "libicuio": -6.687108607866515, "libicuuc": -6.687108607866515, "libnspr4": -6.687108607866515, "libnss3": -6.687108607866515, "libnssutil3": -6.687108607866515, "libpthread": -6.687108607866515, "libraries": -5.77081787599236, "libsmime3": -6.687108607866515, "libx264": -6.687108607866515, "libxcb": -6.687108607866515, "libxkbcommon": -6.687108607866515, "like": -5.77081787599236, "linux": -4.895349138638459, "load": -6.687108607866515, "looks": -5.993961427306569, "lookup": -6.687108607866515, "lot": -6.687108607866515, "make": -6.687108607866515, "malaver": -6.687108607866515, "matching": -6.687108607866515, "may": -6.28164349975835, "meant": -6.687108607866515, "message": -6.687108607866515, "minibrowser": -6.687108607866515, "mint": -5.77081787599236, "missing": -5.077670695432414, "module": -6.687108607866515, "more": -6.687108607866515, "most": -6.28164349975835, "ms": -5.77081787599236, "must": -6.687108607866515, "my": -6.687108607866515, "name": -6.687108607866515, "need": -6.687108607866515, "new": -6.28164349975835, "newer": -6.687108607866515, "no": -6.687108607866515, "non": -6.687108607866515, "not": -5.993961427306569, "npx": -6.28164349975835, "of": -5.434345639371147, "old": -6.687108607866515, "on": -5.18303121109024, "one": -6.28164349975835, "open": -6.687108607866515, "or": -5.77081787599236, "other": -6.687108607866515, "out": -5.77081787599236, "package": -6.687108607866515, "page": -6.687108607866515, "parameter": -6.687108607866515, "path": -6.687108607866515, "platforms": -6.687108607866515, "playwright": -4.435816809260019, "please": -6.28164349975835, "pnpm": -6.687108607866515, "points": -6.687108607866515, "popular": -6.28164349975835, "position": -6.687108607866515, "prompt": -6.687108607866515, "provided": -6.687108607866515, "quick": -6.687108607866515, "react": -6.687108607866515, "related": -6.687108607866515, "replace": -6.687108607866515, "reply": -6.687108607866515, "repositories": -6.28164349975835, "required": -6.687108607866515, "rms": -6.687108607866515, "root": -5.993961427306569, "run": -5.300814246746624, "running": -5.993961427306569, "safari": -6.28164349975835, "same": -5.77081787599236, "seems": -6.687108607866515, "set": -6.687108607866515, "should": -6.28164349975835, "simensbat": -6.687108607866515, "simply": -6.687108607866515, "slt": -6.687108607866515, "so": -3.742669628700074, "some": -6.28164349975835, "spec": -6.28164349975835, "such": -6.687108607866515, "super": -6.687108607866515, "support": -6.687108607866515, "symbol": -6.28164349975835, "syntaxerror": -6.687108607866515, "system": -5.588496319198405, "team": -6.687108607866515, "test": -6.28164349975835, "tests": -6.687108607866515, "the": -4.547042444370244, "there": -5.77081787599236, "this": -5.77081787599236, "time": -6.687108607866515, "title": -6.687108607866515, "tls": -6.687108607866515, "to": -4.489884030530295, "token": -6.687108607866515, "tried": -6.28164349975835, "troubleshooting": -6.687108607866515, "ts": -6.687108607866515, "tsconfig": -6.687108607866515, "ubuntu": -5.588496319198405, "undefined": -5.993961427306569, "unexpected": -6.687108607866515, "unsupported": -5.588496319198405, "updated": -6.28164349975835, "url": -6.687108607866515, "us": -5.588496319198405, "use": -6.687108607866515, "usenglish": -6.687108607866515, "uses": -6.28164349975835, "using": -6.28164349975835, "usr": -6.687108607866515, "value": -6.687108607866515, "values": -6.687108607866515, "variable": -6.28164349975835, "version": -6.28164349975835, "vsts": -6.687108607866515, "was": -5.993961427306569, "way": -6.687108607866515, "webkit": -5.77081787599236, "which": -6.28164349975835, "why": -6.28164349975835, "with": -5.300814246746624, "work": -5.77081787599236, "worker": -6.687108607866515, "works": -6.28164349975835, "would": -6.687108607866515, "x86": -6.687108607866515, "you": -6.28164349975835 }, "flaky-by-retry": { "after": -5.786897381366708, "and": -6.192362489474872, "animation": -6.192362489474872, "attempt": -5.276071757600717, "between": -6.192362489474872, "blip": -6.192362489474872, "cause": -6.192362489474872, "change": -6.192362489474872, "click": -6.192362489474872, "code": -6.192362489474872, "commit": -6.192362489474872, "condition": -6.192362489474872, "dependency": -6.192362489474872, "detached": -6.192362489474872, "deterministic": -6.192362489474872, "element": -6.192362489474872, "failed": -5.499215308914927, "fails": -6.192362489474872, "flaky": -5.276071757600717, "green": -6.192362489474872, "in": -6.192362489474872, "intermittent": -6.192362489474872, "locator": -6.192362489474872, "marked": -6.192362489474872, "network": -6.192362489474872, "no": -6.192362489474872, "non": -6.192362489474872, "on": -5.093750200806762, "order": -6.192362489474872, "passed": -5.093750200806762, "quarantined": -6.192362489474872, "race": -6.192362489474872, "reproducible": -6.192362489474872, "rerun": -6.192362489474872, "retry": -5.276071757600717, "runs": -6.192362489474872, "same": -6.192362489474872, "second": -6.192362489474872, "stale": -6.192362489474872, "succeeded": -6.192362489474872, "test": -5.786897381366708, "the": -6.192362489474872, "then": -6.192362489474872, "timeout": -6.192362489474872, "unstable": -6.192362489474872, "with": -6.192362489474872, "without": -6.192362489474872 }, "locator-not-found": { "25": -6.459904454377535, "30": -5.3612921657094255, "46": -6.459904454377535, "06": -6.459904454377535, "NUM": -5.5436137225033795, "aka": -4.588102277475944, "alert": -6.459904454377535, "all": -6.459904454377535, "an": -6.459904454377535, "api": -6.459904454377535, "aria": -6.05443934626937, "async": -6.459904454377535, "attached": -6.459904454377535, "based": -6.459904454377535, "being": -6.459904454377535, "birth": -6.459904454377535, "btn": -6.459904454377535, "button": -4.588102277475944, "by": -6.459904454377535, "captura": -6.459904454377535, "cart": -6.459904454377535, "carteiras": -6.05443934626937, "cell": -6.05443934626937, "checkout": -6.459904454377535, "checks": -6.459904454377535, "click": -5.5436137225033795, "cns": -5.766757273817589, "columns": -6.459904454377535, "const": -6.459904454377535, "contains": -6.459904454377535, "copy": -6.05443934626937, "count": -6.459904454377535, "cpf": -5.766757273817589, "da": -6.05443934626937, "data": -6.05443934626937, "de": -6.459904454377535, "div": -5.2071414858821665, "does": -6.459904454377535, "dom": -6.459904454377535, "easiest": -6.459904454377535, "element": -5.5436137225033795, "elements": -4.755156362139109, "error": -4.513994305322221, "even": -6.459904454377535, "exact": -5.766757273817589, "expected": -6.459904454377535, "fail": -6.459904454377535, "field": -6.459904454377535, "filtering": -6.459904454377535, "find": -6.459904454377535, "first": -5.766757273817589, "for": -6.05443934626937, "found": -6.05443934626937, "frame": -6.459904454377535, "from": -6.459904454377535, "getbyrole": -5.3612921657094255, "getbytext": -5.073610093257644, "icon": -6.459904454377535, "id": -5.766757273817589, "if": -6.459904454377535, "iframe": -6.459904454377535, "in": -5.766757273817589, "into": -6.459904454377535, "is": -5.5436137225033795, "issue": -6.05443934626937, "it": -5.766757273817589, "javascript": -6.459904454377535, "just": -6.459904454377535, "kb": -6.459904454377535, "label": -6.05443934626937, "licenses": -6.459904454377535, "link": -6.459904454377535, "list": -6.459904454377535, "locator": -4.2626798770413155, "match": -6.459904454377535, "matched": -6.459904454377535, "matches": -6.459904454377535, "mean": -6.459904454377535, "method": -6.459904454377535, "missing": -6.459904454377535, "mode": -4.66814498514948, "mother": -6.459904454377535, "munic": -6.05443934626937, "municipality": -6.459904454377535, "my": -6.459904454377535, "name": -5.2071414858821665, "nascimento": -6.05443934626937, "new": -6.05443934626937, "no": -6.05443934626937, "node": -6.459904454377535, "nome": -5.5436137225033795, "not": -6.05443934626937, "note": -6.459904454377535, "nov": -6.05443934626937, "novalistagem": -6.459904454377535, "nth": -6.459904454377535, "of": -6.459904454377535, "or": -5.766757273817589, "page": -6.459904454377535, "passes": -6.459904454377535, "pio": -6.05443934626937, "playwright": -6.05443934626937, "position": -5.766757273817589, "question": -6.459904454377535, "resolve": -6.459904454377535, "resolved": -4.955827057601261, "response": -6.459904454377535, "rest": -6.459904454377535, "result": -5.766757273817589, "results": -6.459904454377535, "role": -6.459904454377535, "run": -6.459904454377535, "save": -6.459904454377535, "selection": -6.459904454377535, "selector": -6.05443934626937, "simplest": -6.459904454377535, "solution": -6.459904454377535, "span": -5.766757273817589, "specify": -6.459904454377535, "specifying": -6.459904454377535, "state": -6.459904454377535, "strict": -4.66814498514948, "submit": -6.459904454377535, "tabindex": -6.05443934626937, "table": -6.459904454377535, "take": -6.459904454377535, "td": -6.459904454377535, "tela": -6.459904454377535, "test": -6.05443934626937, "testid": -6.459904454377535, "text": -5.766757273817589, "textcontent": -6.459904454377535, "that": -6.05443934626937, "the": -4.513994305322221, "thead": -6.459904454377535, "this": -5.5436137225033795, "title": -6.459904454377535, "to": -4.513994305322221, "total": -6.459904454377535, "tr": -6.459904454377535, "true": -5.766757273817589, "type": -6.05443934626937, "uf": -6.05443934626937, "unable": -6.459904454377535, "validate": -6.459904454377535, "validatefieldvalue": -6.459904454377535, "validation": -6.459904454377535, "violation": -4.755156362139109, "waitfor": -6.459904454377535, "waiting": -6.459904454377535, "we": -6.459904454377535, "what": -6.459904454377535, "when": -6.459904454377535, "whether": -6.459904454377535, "will": -6.459904454377535, "with": -6.459904454377535, "xpath": -6.459904454377535 }, "navigation": { "14": -6.304448802421981, "19": -6.709913910530146, "25": -6.709913910530146, "27": -6.304448802421981, "39": -6.709913910530146, "49": -6.709913910530146, "66": -6.709913910530146, "0ms": -6.304448802421981, "6ms": -6.709913910530146, "NUM": -4.569847747033875, "URL": -5.793623178655991, "abort": -6.0167667299702, "aborted": -4.91815444130209, "acting": -6.709913910530146, "additional": -6.709913910530146, "adjusting": -6.709913910530146, "after": -6.0167667299702, "an": -6.304448802421981, "and": -6.304448802421981, "another": -6.709913910530146, "api": -5.611301621862036, "appends": -6.709913910530146, "are": -6.709913910530146, "argument": -6.709913910530146, "as": -6.709913910530146, "async": -6.0167667299702, "at": -5.457150942034778, "avoid": -6.709913910530146, "await": -4.91815444130209, "back": -6.709913910530146, "base": -6.709913910530146, "be": -6.709913910530146, "because": -5.457150942034778, "before": -6.304448802421981, "behavior": -6.709913910530146, "being": -6.709913910530146, "both": -6.709913910530146, "browser": -6.0167667299702, "bug": -6.304448802421981, "but": -6.304448802421981, "by": -6.0167667299702, "call": -5.457150942034778, "callback": -6.709913910530146, "called": -6.709913910530146, "calling": -6.709913910530146, "cancelled": -6.709913910530146, "cannot": -6.709913910530146, "caused": -6.709913910530146, "causes": -6.709913910530146, "certain": -6.709913910530146, "chain": -6.304448802421981, "channel": -6.709913910530146, "checkout": -6.709913910530146, "chrome": -6.709913910530146, "click": -6.709913910530146, "close": -6.709913910530146, "closed": -6.709913910530146, "code": -6.709913910530146, "comment": -6.709913910530146, "commented": -6.709913910530146, "common": -6.304448802421981, "complicated": -6.709913910530146, "connection": -6.709913910530146, "const": -6.709913910530146, "context": -6.304448802421981, "copy": -6.0167667299702, "crashed": -5.793623178655991, "create": -6.709913910530146, "dashboard": -6.0167667299702, "debugging": -6.709913910530146, "describe": -6.709913910530146, "destroyed": -6.0167667299702, "detached": -5.100475998096045, "developer": -6.709913910530146, "didn": -6.709913910530146, "different": -6.709913910530146, "directly": -6.709913910530146, "document": -6.709913910530146, "does": -6.709913910530146, "domcontentloaded": -6.304448802421981, "dowrite": -6.709913910530146, "during": -6.304448802421981, "en": -6.709913910530146, "entry": -6.709913910530146, "err": -4.695010889987881, "error": -4.838111733628554, "event": -6.304448802421981, "example": -6.709913910530146, "exceeded": -6.304448802421981, "execution": -6.709913910530146, "expect": -6.709913910530146, "expected": -6.709913910530146, "facing": -6.709913910530146, "failed": -5.611301621862036, "file": -5.323619549410255, "fine": -6.304448802421981, "fired": -6.304448802421981, "firefox": -6.709913910530146, "for": -5.793623178655991, "frame": -4.6304723688503096, "frames": -6.709913910530146, "frequent": -6.709913910530146, "from": -6.709913910530146, "frompromise": -6.709913910530146, "full": -6.709913910530146, "future": -6.709913910530146, "getbyrole": -6.709913910530146, "getting": -6.709913910530146, "given": -6.709913910530146, "goback": -6.709913910530146, "going": -6.709913910530146, "goto": -4.312018637731775, "handler": -6.709913910530146, "happens": -6.709913910530146, "have": -6.709913910530146, "headless": -6.709913910530146, "helper": -6.0167667299702, "hint": -6.709913910530146, "history": -6.709913910530146, "home": -5.323619549410255, "html": -6.0167667299702, "impl": -6.709913910530146, "in": -5.457150942034778, "include": -6.709913910530146, "info": -6.709913910530146, "initial": -6.709913910530146, "innerhtml": -6.709913910530146, "inside": -6.709913910530146, "instead": -6.709913910530146, "internal": -6.709913910530146, "interrupted": -6.304448802421981, "is": -6.304448802421981, "issue": -5.457150942034778, "it": -6.304448802421981, "its": -6.709913910530146, "js": -5.611301621862036, "jul": -6.304448802421981, "lang": -6.709913910530146, "last": -6.304448802421981, "lib": -5.793623178655991, "likely": -6.709913910530146, "line": -5.457150942034778, "link": -5.793623178655991, "load": -5.793623178655991, "loads": -6.304448802421981, "locals": -5.793623178655991, "log": -6.0167667299702, "login": -6.0167667299702, "logs": -6.304448802421981, "main": -6.709913910530146, "many": -6.304448802421981, "maxschmitt": -6.709913910530146, "may": -6.709913910530146, "maybe": -5.100475998096045, "means": -6.709913910530146, "message": -6.709913910530146, "method": -6.709913910530146, "mistake": -6.709913910530146, "mjs": -6.709913910530146, "mobile": -6.709913910530146, "mode": -6.709913910530146, "more": -6.709913910530146, "most": -6.0167667299702, "my": -6.709913910530146, "name": -6.304448802421981, "navigate": -6.709913910530146, "navigates": -6.709913910530146, "navigatetojobs": -6.709913910530146, "navigating": -5.323619549410255, "navigation": -4.695010889987881, "net": -4.838111733628554, "new": -5.205836513753871, "next": -6.709913910530146, "no": -6.304448802421981, "node": -6.0167667299702, "non": -6.709913910530146, "none": -6.709913910530146, "oauth": -6.709913910530146, "obj": -6.709913910530146, "of": -5.457150942034778, "on": -6.0167667299702, "open": -6.304448802421981, "options": -6.709913910530146, "or": -6.304448802421981, "other": -6.709913910530146, "out": -6.709913910530146, "packages": -5.793623178655991, "page": -3.9065535296236105, "pages": -6.0167667299702, "params": -6.304448802421981, "per": -6.709913910530146, "playwright": -4.91815444130209, "pretty": -6.709913910530146, "process": -6.709913910530146, "promises": -6.709913910530146, "pw": -5.793623178655991, "py": -5.457150942034778, "python3": -5.793623178655991, "randomly": -6.709913910530146, "rebuilds": -6.709913910530146, "received": -6.709913910530146, "recent": -6.304448802421981, "redirect": -6.0167667299702, "redirects": -6.709913910530146, "reload": -6.304448802421981, "removed": -6.304448802421981, "removes": -6.709913910530146, "replaced": -6.709913910530146, "replaces": -6.709913910530146, "replicate": -6.709913910530146, "request": -6.709913910530146, "resolved": -6.709913910530146, "response": -6.709913910530146, "result": -6.709913910530146, "retrieving": -6.709913910530146, "return": -6.709913910530146, "returns": -6.709913910530146, "root": -6.709913910530146, "route": -6.0167667299702, "run": -6.709913910530146, "same": -6.709913910530146, "script": -6.304448802421981, "selector": -6.709913910530146, "self": -5.611301621862036, "send": -6.304448802421981, "sends": -6.709913910530146, "settings": -6.304448802421981, "site": -5.793623178655991, "skip": -6.709913910530146, "socket": -6.709913910530146, "some": -6.304448802421981, "somepage": -6.709913910530146, "spa": -6.304448802421981, "started": -6.709913910530146, "states": -6.709913910530146, "stcpage": -6.304448802421981, "stcunregisteredpage": -6.709913910530146, "stephen100mwangi": -6.709913910530146, "stream": -5.793623178655991, "suddenly": -6.709913910530146, "suspects": -6.709913910530146, "test": -5.457150942034778, "tests": -6.709913910530146, "than": -6.709913910530146, "that": -5.793623178655991, "the": -4.695010889987881, "then": -6.709913910530146, "there": -6.709913910530146, "thing": -6.709913910530146, "this": -6.709913910530146, "through": -6.709913910530146, "timeout": -6.304448802421981, "to": -4.4073288175361, "too": -6.709913910530146, "totally": -6.709913910530146, "traceback": -6.0167667299702, "tried": -6.709913910530146, "triggers": -6.709913910530146, "triggeruncaughtexception": -6.709913910530146, "true": -6.709913910530146, "trying": -6.709913910530146, "uid": -6.709913910530146, "unhandledpromiserejectionwarning": -6.709913910530146, "until": -5.793623178655991, "url": -5.611301621862036, "users": -6.709913910530146, "v18": -6.709913910530146, "variant": -6.304448802421981, "venv": -5.793623178655991, "wait": -6.709913910530146, "waitforurl": -6.304448802421981, "waiting": -6.0167667299702, "waituntil": -6.709913910530146, "was": -4.6304723688503096, "way": -6.709913910530146, "website": -6.709913910530146, "were": -6.709913910530146, "when": -6.0167667299702, "which": -6.709913910530146, "while": -6.304448802421981, "with": -6.0167667299702, "worked": -6.709913910530146, "works": -6.709913910530146, "writable": -6.0167667299702, "write": -6.304448802421981, "writeorbuffer": -6.709913910530146, "your": -6.709913910530146 }, "network": { "10": -5.903316160331277, "11": -5.680172609017068, "12": -4.804703871663167, "13": -6.190998232783058, "14": -4.345171542284727, "15": -4.345171542284727, "16": -5.343700372395855, "17": -4.345171542284727, "19": -6.596463340891223, "20": -6.596463340891223, "21": -6.190998232783058, "23": -6.596463340891223, "31": -5.210168979771332, "34": -6.596463340891223, "48": -6.596463340891223, "51": -6.596463340891223, "61": -6.596463340891223, "64": -6.596463340891223, "65": -6.596463340891223, "69": -6.596463340891223, "77": -6.596463340891223, "95": -6.596463340891223, "98": -6.596463340891223, "NUM": -5.210168979771332, "URL": -4.345171542284727, "access": -6.190998232783058, "again": -6.596463340891223, "am": -6.596463340891223, "and": -6.190998232783058, "anonymous": -6.596463340891223, "api": -6.190998232783058, "apirequestcontext": -6.596463340891223, "applicationurl": -6.596463340891223, "async": -6.190998232783058, "at": -4.070734696582967, "await": -5.903316160331277, "basepage": -6.596463340891223, "before": -6.190998232783058, "beforeeach": -6.596463340891223, "body": -6.596463340891223, "bundle": -6.190998232783058, "capture": -6.596463340891223, "capturestacktrace": -6.596463340891223, "channelowner": -6.190998232783058, "check": -5.680172609017068, "checkout": -6.596463340891223, "chromium": -6.596463340891223, "client": -5.497851052223113, "clientrequest": -6.190998232783058, "close": -6.596463340891223, "code": -6.190998232783058, "common": -6.596463340891223, "connect": -6.596463340891223, "connection": -4.8917152486527975, "const": -6.596463340891223, "dashboardpage": -5.343700372395855, "day": -6.596463340891223, "debug": -6.596463340891223, "deliver": -6.596463340891223, "disconnected": -6.596463340891223, "domcontentloaded": -6.190998232783058, "each": -6.596463340891223, "eai": -6.596463340891223, "econnrefused": -6.596463340891223, "econnreset": -6.596463340891223, "emit": -6.190998232783058, "environment": -6.596463340891223, "err": -4.650553191835909, "error": -4.581560320348958, "events": -6.190998232783058, "failed": -5.903316160331277, "fetch": -6.596463340891223, "fetching": -6.596463340891223, "for": -6.596463340891223, "frame": -5.680172609017068, "from": -6.596463340891223, "get": -6.596463340891223, "getaddrinfo": -6.596463340891223, "git": -6.190998232783058, "goto": -4.650553191835909, "gotosearchproduct": -6.596463340891223, "handler": -6.596463340891223, "hang": -6.190998232783058, "hook": -6.596463340891223, "http": -6.596463340891223, "internet": -6.596463340891223, "js": -4.581560320348958, "lib": -5.343700372395855, "load": -6.190998232783058, "loadapplication": -6.596463340891223, "loading": -6.596463340891223, "login": -5.680172609017068, "logs": -5.092385944114948, "modules": -4.724661163989631, "name": -5.497851052223113, "navigating": -5.210168979771332, "net": -4.517021799211387, "networkidle": -5.680172609017068, "new": -5.903316160331277, "node": -4.724661163989631, "not": -5.903316160331277, "note": -6.596463340891223, "object": -6.596463340891223, "on": -6.190998232783058, "out": -6.190998232783058, "page": -4.456397177394951, "pages": -6.596463340891223, "personal": -6.190998232783058, "playwright": -4.154116305522018, "pnpm": -5.343700372395855, "product": -6.596463340891223, "producteditpage": -6.190998232783058, "products": -6.596463340891223, "productsearchpage": -6.190998232783058, "project": -6.190998232783058, "promises": -6.596463340891223, "protocol": -6.596463340891223, "proxy": -6.596463340891223, "proxyerror": -6.596463340891223, "public": -6.596463340891223, "pw": -6.596463340891223, "reading": -6.596463340891223, "reason": -6.596463340891223, "recording": -6.596463340891223, "refused": -5.343700372395855, "request": -6.190998232783058, "resolved": -5.903316160331277, "resource": -6.596463340891223, "response": -6.596463340891223, "route": -6.596463340891223, "runner": -6.190998232783058, "same": -6.596463340891223, "sauce": -6.190998232783058, "saucelabintegration": -6.596463340891223, "scheduleedit": -6.596463340891223, "sendmessagetoserver": -6.596463340891223, "server": -6.596463340891223, "settings": -6.596463340891223, "single": -6.596463340891223, "socket": -5.680172609017068, "socketcloselistener": -6.596463340891223, "spec": -6.190998232783058, "ssl": -6.596463340891223, "stacktrace": -6.596463340891223, "started": -6.596463340891223, "starting": -6.596463340891223, "tcp": -6.596463340891223, "test": -6.596463340891223, "tests": -5.680172609017068, "this": -6.596463340891223, "timed": -6.190998232783058, "timeouts": -6.596463340891223, "to": -4.987025428457122, "token": -6.190998232783058, "trace": -6.596463340891223, "ts": -6.596463340891223, "until": -5.210168979771332, "up": -6.190998232783058, "update": -6.190998232783058, "upstream": -6.596463340891223, "url": -6.190998232783058, "use": -6.596463340891223, "user": -5.903316160331277, "utils": -6.596463340891223, "valueof": -6.596463340891223, "variable": -6.596463340891223, "waiting": -5.210168979771332, "waituntil": -6.596463340891223, "while": -6.596463340891223, "wo": -6.596463340891223, "work": -5.343700372395855, "wrapapicall": -6.596463340891223, "your": -6.596463340891223 }, "timeout": { "10": -6.434546518787453, "15": -6.840011626895618, "16": -6.840011626895618, "17": -6.840011626895618, "18": -6.840011626895618, "19": -6.840011626895618, "20": -6.434546518787453, "21": -6.434546518787453, "22": -6.840011626895618, "23": -6.840011626895618, "30": -5.741399338227508, "31": -6.840011626895618, "54": -6.840011626895618, "58": -6.840011626895618, "59": -6.840011626895618, "60": -6.146864446335672, "61": -6.434546518787453, "62": -6.840011626895618, "63": -6.840011626895618, "64": -6.840011626895618, "73": -6.840011626895618, "79": -6.840011626895618, "83": -6.840011626895618, "00": -6.434546518787453, "NUM": -3.7719586917620003, "URL": -6.434546518787453, "action": -5.453717265775727, "actiontimeout": -6.840011626895618, "actual": -6.146864446335672, "after": -6.840011626895618, "afterall": -6.840011626895618, "all": -6.840011626895618, "amount": -6.840011626895618, "an": -5.741399338227508, "and": -5.135263534657192, "angle": -6.840011626895618, "anush": -6.434546518787453, "anzeigen": -6.840011626895618, "api": -5.923720895021463, "app": -6.840011626895618, "appeared": -6.840011626895618, "apply": -6.840011626895618, "are": -6.840011626895618, "areas": -6.840011626895618, "aren": -6.840011626895618, "as": -6.840011626895618, "assertions": -6.840011626895618, "async": -6.840011626895618, "at": -4.968209449994026, "attempt": -6.840011626895618, "attempting": -6.434546518787453, "attributetopage": -6.840011626895618, "auth": -6.146864446335672, "av": -6.434546518787453, "available": -6.840011626895618, "await": -5.453717265775727, "baseurl": -6.434546518787453, "basic": -6.146864446335672, "be": -5.741399338227508, "been": -5.923720895021463, "beforeall": -6.840011626895618, "beforeeach": -6.434546518787453, "between": -6.840011626895618, "bin": -6.840011626895618, "binding": -6.840011626895618, "br": -4.825108606353353, "browser": -5.58724865840025, "browsertype": -6.840011626895618, "btn": -6.840011626895618, "but": -6.840011626895618, "button": -5.923720895021463, "by": -6.434546518787453, "cache": -6.434546518787453, "call": -6.434546518787453, "can": -6.840011626895618, "capture": -6.840011626895618, "cart": -6.840011626895618, "case": -6.434546518787453, "certificates": -6.840011626895618, "chromium": -5.335934230119343, "class": -6.840011626895618, "click": -5.135263534657192, "closed": -5.230573714461517, "config": -5.58724865840025, "configured": -6.840011626895618, "const": -5.923720895021463, "consume": -6.840011626895618, "context": -5.923720895021463, "copy": -6.840011626895618, "core20": -6.840011626895618, "correlation": -6.840011626895618, "could": -6.146864446335672, "ctaheader": -6.840011626895618, "current": -6.840011626895618, "data": -6.146864446335672, "debug": -6.840011626895618, "default": -6.434546518787453, "defer": -6.840011626895618, "describe": -6.840011626895618, "deutschland": -6.434546518787453, "div": -6.146864446335672, "domcontentloaded": -6.434546518787453, "done": -6.840011626895618, "down": -6.840011626895618, "dropdown": -6.840011626895618, "during": -6.434546518787453, "each": -6.434546518787453, "element": -5.741399338227508, "enabled": -5.58724865840025, "entire": -6.840011626895618, "environment": -6.434546518787453, "err": -6.840011626895618, "error": -4.825108606353353, "event": -6.146864446335672, "exceeded": -4.699945463399347, "expect": -6.146864446335672, "fail": -6.840011626895618, "fill": -6.840011626895618, "filter": -6.840011626895618, "fired": -6.840011626895618, "first": -6.840011626895618, "fixture": -6.146864446335672, "for": -4.588719828289123, "force": -6.840011626895618, "form": -5.741399338227508, "frame": -6.434546518787453, "frames": -6.434546518787453, "from": -6.146864446335672, "function": -6.840011626895618, "glibc": -6.840011626895618, "global": -6.434546518787453, "globaltimeout": -6.840011626895618, "gnu": -6.840011626895618, "goto": -5.230573714461517, "gtk": -6.840011626895618, "has": -5.741399338227508, "hastext": -6.434546518787453, "have": -6.434546518787453, "helper": -6.840011626895618, "hold": -6.434546518787453, "home": -6.146864446335672, "hook": -6.146864446335672, "httpcredentials": -6.434546518787453, "icon": -6.840011626895618, "id": -6.840011626895618, "if": -6.146864446335672, "in": -4.825108606353353, "inaccurate": -6.146864446335672, "indexof": -6.840011626895618, "individual": -6.840011626895618, "init": -6.840011626895618, "insert": -6.840011626895618, "inspector": -6.840011626895618, "into": -6.434546518787453, "invalid": -6.840011626895618, "is": -5.048252157667562, "isn": -6.840011626895618, "issues": -6.840011626895618, "it": -6.434546518787453, "javascript": -6.840011626895618, "js": -5.230573714461517, "just": -5.923720895021463, "label": -6.840011626895618, "launch": -6.840011626895618, "league": -6.840011626895618, "leagues": -6.840011626895618, "lib": -5.335934230119343, "libc": -6.840011626895618, "libpthread": -6.840011626895618, "likely": -6.840011626895618, "link": -6.840011626895618, "linux": -6.840011626895618, "load": -6.434546518787453, "loading": -6.840011626895618, "locator": -4.699945463399347, "log": -6.434546518787453, "logs": -5.923720895021463, "lookup": -6.840011626895618, "may": -6.840011626895618, "message": -6.840011626895618, "minibrowser": -6.434546518787453, "minutes": -6.840011626895618, "module": -6.840011626895618, "modules": -5.453717265775727, "more": -6.840011626895618, "ms": -5.923720895021463, "my": -6.840011626895618, "navigated": -6.840011626895618, "navigating": -6.840011626895618, "navigation": -5.335934230119343, "navigationtimeout": -6.840011626895618, "needed": -6.840011626895618, "network": -6.840011626895618, "new": -6.840011626895618, "newcontext": -6.840011626895618, "news": -6.840011626895618, "ng": -6.434546518787453, "no": -5.453717265775727, "node": -5.335934230119343, "not": -5.923720895021463, "note": -6.840011626895618, "nth": -6.840011626895618, "occur": -6.840011626895618, "of": -5.230573714461517, "on": -6.434546518787453, "open": -6.146864446335672, "option": -6.840011626895618, "options": -5.923720895021463, "or": -5.741399338227508, "out": -6.840011626895618, "override": -6.434546518787453, "page": -4.1658629774690885, "pages": -6.840011626895618, "password": -5.923720895021463, "pbutton": -6.840011626895618, "pid": -6.434546518787453, "pipe": -6.840011626895618, "playwright": -4.642787049559399, "private": -6.840011626895618, "problematic": -6.840011626895618, "progress": -6.840011626895618, "progresscontroller": -6.840011626895618, "protocol": -6.840011626895618, "pthread": -6.840011626895618, "pw": -6.434546518787453, "question": -6.840011626895618, "rather": -6.840011626895618, "reason": -6.146864446335672, "regexp": -6.840011626895618, "release": -6.434546518787453, "rerun": -6.840011626895618, "resolved": -6.434546518787453, "retrieve": -6.840011626895618, "retry": -6.840011626895618, "run": -6.146864446335672, "runnavigationtask": -6.840011626895618, "running": -6.434546518787453, "say": -6.840011626895618, "scope": -6.840011626895618, "scrolling": -6.434546518787453, "seconds": -6.840011626895618, "select": -6.146864446335672, "selector": -5.923720895021463, "self": -6.840011626895618, "session": -6.840011626895618, "set": -5.58724865840025, "sets": -6.840011626895618, "settimeout": -6.840011626895618, "sh": -6.840011626895618, "share": -6.434546518787453, "show": -6.840011626895618, "sidebar": -6.434546518787453, "signed": -6.840011626895618, "similar": -6.840011626895618, "single": -6.840011626895618, "site": -6.146864446335672, "slow": -6.840011626895618, "snap": -6.840011626895618, "so": -6.840011626895618, "sometimes": -6.840011626895618, "span": -6.840011626895618, "spec": -6.434546518787453, "specific": -6.434546518787453, "src": -6.840011626895618, "ssl": -6.840011626895618, "stable": -6.146864446335672, "startup": -6.840011626895618, "statement": -6.840011626895618, "status": -6.840011626895618, "stuck": -6.146864446335672, "submit": -6.840011626895618, "symbol": -6.434546518787453, "target": -4.825108606353353, "teaser": -6.840011626895618, "test": -4.642787049559399, "tests": -6.146864446335672, "text": -6.434546518787453, "that": -5.923720895021463, "the": -4.314282982587362, "there": -5.923720895021463, "this": -5.923720895021463, "throw": -6.840011626895618, "throws": -6.840011626895618, "time": -6.840011626895618, "timed": -6.840011626895618, "timeout": -3.9222408948113383, "timeouterror": -5.923720895021463, "timeouts": -5.923720895021463, "to": -4.588719828289123, "tobevisible": -6.840011626895618, "tried": -6.840011626895618, "ts": -6.434546518787453, "undefined": -6.840011626895618, "unhandledpromiserejectionwarning": -6.840011626895618, "until": -6.840011626895618, "url": -5.923720895021463, "use": -5.923720895021463, "user": -6.434546518787453, "username": -6.434546518787453, "using": -6.840011626895618, "usr": -6.840011626895618, "variable": -6.840011626895618, "version": -6.840011626895618, "view": -6.840011626895618, "visible": -5.453717265775727, "wait": -6.840011626895618, "waitforloadstate": -6.840011626895618, "waitforselector": -6.434546518787453, "waitfortimeout": -6.434546518787453, "waiting": -4.894101477840304, "waituntil": -6.840011626895618, "wamdata": -6.840011626895618, "was": -5.58724865840025, "way": -6.840011626895618, "we": -6.840011626895618, "webkit": -6.434546518787453, "when": -6.840011626895618, "while": -6.146864446335672, "whole": -6.840011626895618, "will": -6.840011626895618, "window": -6.840011626895618, "with": -6.146864446335672, "wrapper": -5.923720895021463, "x86": -6.840011626895618, "yet": -6.840011626895618, "your": -6.434546518787453 } }, "playwrightVersion": "1.61.1", "priors": { "assertion": -2.2472349979108808, "env-config": -1.6766901394432678, "flaky-by-retry": -2.7327428136925813, "locator-not-found": -2.0395956331326364, "navigation": -1.7211419020141017, "network": -2.1041341542702074, "timeout": -1.5540878173509354 }, "totals": { "assertion": 323, "env-config": 694, "flaky-by-retry": 68, "locator-not-found": 368, "navigation": 731, "network": 555, "timeout": 959 }, "version": 3, "vocabSize": 910 };
12165
+ }
12166
+ });
12167
+
12168
+ // src/analysis/tokenize.ts
12169
+ function tokenize(text) {
12170
+ 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);
12171
+ }
12172
+ var init_tokenize = __esm({
12173
+ "src/analysis/tokenize.ts"() {
12174
+ "use strict";
12175
+ init_cjs_shims();
12176
+ }
12177
+ });
12178
+
12179
+ // src/analysis/NaiveBayesClassifier.ts
12180
+ function classify(model, text) {
12181
+ return classifyTokens(model, tokenize(text));
12182
+ }
12183
+ function classifyTokens(model, tokens) {
12184
+ const vocabTokens = tokens.filter(
12185
+ (tok) => tok.length > 0 && model.labels.some((l) => model.logLikelihoods[l]?.[tok] !== void 0)
12186
+ );
12187
+ if (vocabTokens.length === 0) {
12188
+ return { label: "unknown", margin: 0 };
12189
+ }
12190
+ const scores = {};
12191
+ for (const label of model.labels) {
12192
+ let s = model.priors[label];
12193
+ const oov = Math.log(model.alpha / (model.totals[label] + model.alpha * model.vocabSize));
12194
+ const ll = model.logLikelihoods[label];
12195
+ for (const tok of vocabTokens) {
12196
+ s += ll[tok] ?? oov;
12197
+ }
12198
+ scores[label] = s;
12199
+ }
12200
+ const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
12201
+ const topLog = ranked[0][1];
12202
+ const secondLog = ranked[1]?.[1] ?? topLog;
12203
+ const margin = (topLog - secondLog) / vocabTokens.length;
12204
+ return { label: ranked[0][0], margin };
12205
+ }
12206
+ function isValidNaiveBayesModel(m) {
12207
+ const x = m;
12208
+ 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";
12209
+ }
12210
+ var init_NaiveBayesClassifier = __esm({
12211
+ "src/analysis/NaiveBayesClassifier.ts"() {
12212
+ "use strict";
12213
+ init_cjs_shims();
12214
+ init_tokenize();
12157
12215
  }
12158
12216
  });
12159
12217
 
@@ -12178,10 +12236,6 @@ function verifyModelSignature(modelJson, signatureB64url) {
12178
12236
  return false;
12179
12237
  }
12180
12238
  }
12181
- function schemaValid(m) {
12182
- const x = m;
12183
- 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";
12184
- }
12185
12239
  function readCache() {
12186
12240
  try {
12187
12241
  const raw = import_node_fs.default.readFileSync(cacheFile(), "utf8");
@@ -12210,7 +12264,7 @@ function loadModel(autoUpdate) {
12210
12264
  }
12211
12265
  return MODEL;
12212
12266
  }
12213
- var import_node_fs, import_node_os, import_node_path, import_node_crypto, MODEL_SIG_DOMAIN, testDeps;
12267
+ var import_node_fs, import_node_os, import_node_path, import_node_crypto, MODEL_SIG_DOMAIN, testDeps, schemaValid;
12214
12268
  var init_ModelStore = __esm({
12215
12269
  "src/analysis/ModelStore.ts"() {
12216
12270
  "use strict";
@@ -12221,62 +12275,114 @@ var init_ModelStore = __esm({
12221
12275
  import_node_crypto = require("crypto");
12222
12276
  init_pubkey_bundle();
12223
12277
  init_model_bundle();
12278
+ init_NaiveBayesClassifier();
12224
12279
  init_logger();
12225
12280
  MODEL_SIG_DOMAIN = "reportforge-model-sig:v1\n";
12226
12281
  testDeps = null;
12282
+ schemaValid = isValidNaiveBayesModel;
12227
12283
  }
12228
12284
  });
12229
12285
 
12230
- // src/analysis/tokenize.ts
12231
- function tokenize(text) {
12232
- 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);
12286
+ // src/analysis/LocalModelStore.ts
12287
+ function defaultLocalModelPath() {
12288
+ return testDeps2?.file ?? import_node_path2.default.join(import_node_os2.default.homedir(), ".reportforge", "model-local.json");
12233
12289
  }
12234
- var init_tokenize = __esm({
12235
- "src/analysis/tokenize.ts"() {
12290
+ function loadLocalModel(overridePath) {
12291
+ const file = overridePath ?? defaultLocalModelPath();
12292
+ const explicit = overridePath !== void 0;
12293
+ const reject = (why) => {
12294
+ const msg = `local model at ${file} not used: ${why}`;
12295
+ if (explicit) logger.warn(msg);
12296
+ else logger.debug(msg);
12297
+ return null;
12298
+ };
12299
+ let parsed;
12300
+ try {
12301
+ parsed = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
12302
+ } catch (e) {
12303
+ return explicit ? reject(`unreadable (${e.message})`) : null;
12304
+ }
12305
+ if (parsed.formatVersion !== 1) return reject(`unsupported formatVersion ${String(parsed.formatVersion)}`);
12306
+ if (!isValidNaiveBayesModel(parsed.model)) return reject("embedded model failed schema validation");
12307
+ const meta = parsed.meta;
12308
+ if (!meta || typeof meta.gatePassed !== "boolean") return reject("missing meta");
12309
+ if (!meta.gatePassed) return reject("its evaluation gate did not pass - run train-model after labeling more feedback");
12310
+ if (typeof meta.acceptThreshold !== "number") return reject("gate passed but no calibrated threshold (corrupt meta)");
12311
+ return {
12312
+ model: parsed.model,
12313
+ acceptThreshold: meta.acceptThreshold,
12314
+ strongThreshold: typeof meta.strongThreshold === "number" ? meta.strongThreshold : null,
12315
+ meta
12316
+ };
12317
+ }
12318
+ function saveLocalModel(fileContents, outPath) {
12319
+ const file = outPath ?? defaultLocalModelPath();
12320
+ import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
12321
+ const tmp = `${file}.${process.pid}.tmp`;
12322
+ import_node_fs2.default.writeFileSync(tmp, JSON.stringify(fileContents, null, 2), { mode: 384 });
12323
+ import_node_fs2.default.renameSync(tmp, file);
12324
+ return file;
12325
+ }
12326
+ function readLocalModelFile(overridePath) {
12327
+ try {
12328
+ const parsed = JSON.parse(import_node_fs2.default.readFileSync(overridePath ?? defaultLocalModelPath(), "utf8"));
12329
+ return parsed.formatVersion === 1 && isValidNaiveBayesModel(parsed.model) ? parsed : null;
12330
+ } catch {
12331
+ return null;
12332
+ }
12333
+ }
12334
+ var import_node_fs2, import_node_os2, import_node_path2, testDeps2;
12335
+ var init_LocalModelStore = __esm({
12336
+ "src/analysis/LocalModelStore.ts"() {
12236
12337
  "use strict";
12237
12338
  init_cjs_shims();
12339
+ import_node_fs2 = __toESM(require("fs"));
12340
+ import_node_os2 = __toESM(require("os"));
12341
+ import_node_path2 = __toESM(require("path"));
12342
+ init_NaiveBayesClassifier();
12343
+ init_logger();
12344
+ testDeps2 = null;
12238
12345
  }
12239
12346
  });
12240
12347
 
12241
- // src/analysis/NaiveBayesClassifier.ts
12242
- function classify(model, text) {
12243
- const tokens = tokenize(text);
12244
- const vocabTokens = tokens.filter(
12245
- (tok) => model.labels.some((l) => model.logLikelihoods[l]?.[tok] !== void 0)
12246
- );
12247
- if (vocabTokens.length === 0) {
12248
- return { label: "unknown", margin: 0 };
12249
- }
12250
- const scores = {};
12251
- for (const label of model.labels) {
12252
- let s = model.priors[label];
12253
- const oov = Math.log(model.alpha / (model.totals[label] + model.alpha * model.vocabSize));
12254
- const ll = model.logLikelihoods[label];
12255
- for (const tok of vocabTokens) {
12256
- s += ll[tok] ?? oov;
12257
- }
12258
- scores[label] = s;
12259
- }
12260
- const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
12261
- const topLog = ranked[0][1];
12262
- const secondLog = ranked[1]?.[1] ?? topLog;
12263
- const margin = (topLog - secondLog) / vocabTokens.length;
12264
- return { label: ranked[0][0], margin };
12348
+ // src/analysis/redact.ts
12349
+ function redactForStorage(raw) {
12350
+ 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 ");
12351
+ return tokenize(redacted).join(" ");
12265
12352
  }
12266
- var init_NaiveBayesClassifier = __esm({
12267
- "src/analysis/NaiveBayesClassifier.ts"() {
12353
+ function localInferenceTokens(message) {
12354
+ return redactForStorage(message).split(" ").filter(Boolean);
12355
+ }
12356
+ var URI_RE, EMAIL_RE, SECRET_RE, IP_RE, WIN_PATH_RE, UNIX_PATH_RE;
12357
+ var init_redact = __esm({
12358
+ "src/analysis/redact.ts"() {
12268
12359
  "use strict";
12269
12360
  init_cjs_shims();
12270
12361
  init_tokenize();
12362
+ URI_RE = /[a-z][\w+.-]*:\/\/\S+/gi;
12363
+ EMAIL_RE = /\b[\w.+-]+@[\w.-]+\.\w+\b/g;
12364
+ SECRET_RE = /\b(?=[A-Za-z0-9_+/=-]*[0-9])(?=[A-Za-z0-9_+/=-]*[A-Za-z])[A-Za-z0-9_+/=-]{16,}\b/g;
12365
+ IP_RE = /\b\d{1,3}(?:\.\d{1,3}){3}\b/g;
12366
+ WIN_PATH_RE = /[A-Za-z]:\\[^\s'"]+/g;
12367
+ UNIX_PATH_RE = /(?:\/[\w.-]+){2,}/g;
12271
12368
  }
12272
12369
  });
12273
12370
 
12274
12371
  // src/analysis/constants.ts
12275
- var STRENGTH, STRENGTH_LEVELS, OVERRIDE_MARGIN;
12372
+ var CATEGORIES, STRENGTH, STRENGTH_LEVELS, OVERRIDE_MARGIN, LOCAL_TRAINING;
12276
12373
  var init_constants = __esm({
12277
12374
  "src/analysis/constants.ts"() {
12278
12375
  "use strict";
12279
12376
  init_cjs_shims();
12377
+ CATEGORIES = [
12378
+ "assertion",
12379
+ "env-config",
12380
+ "flaky-by-retry",
12381
+ "locator-not-found",
12382
+ "navigation",
12383
+ "network",
12384
+ "timeout"
12385
+ ];
12280
12386
  STRENGTH = {
12281
12387
  unknownMaxMargin: 0.05,
12282
12388
  collectMaxMargin: 0.1,
@@ -12286,6 +12392,23 @@ var init_constants = __esm({
12286
12392
  };
12287
12393
  STRENGTH_LEVELS = ["weak", "moderate", "strong"];
12288
12394
  OVERRIDE_MARGIN = 999;
12395
+ LOCAL_TRAINING = {
12396
+ /** NB priors from fewer docs are noise, and LOOCV estimates below this are junk. */
12397
+ minLabeled: 10,
12398
+ /** A margin needs a runner-up label to exist at all. */
12399
+ minClasses: 2,
12400
+ /** Precision estimated on fewer accepted predictions is noise — refuse to calibrate. */
12401
+ minAccepted: 5,
12402
+ /** The local layer only ANSWERS when its held-out precision at the chosen threshold hits this. */
12403
+ acceptPrecision: 0.9,
12404
+ /** Bar for the local layer to render as 'strong' (else capped at 'moderate'). */
12405
+ strongPrecision: 0.95,
12406
+ /** Below this n, leave-one-out (5-fold folds would be too small to train on). */
12407
+ cvSwitchoverN: 50,
12408
+ folds: 5,
12409
+ /** Fixed PRNG seed — evaluation must be reproducible run-to-run. */
12410
+ seed: 1234
12411
+ };
12289
12412
  }
12290
12413
  });
12291
12414
 
@@ -12306,7 +12429,7 @@ function errorHeader(message) {
12306
12429
  return beforeCallLog.split("\n").filter((line) => !/^\s*[-+]?\s*(?:Expected|Received)\b/i.test(line)).join("\n");
12307
12430
  }
12308
12431
  function isAssertionTimeout(message) {
12309
- return /Expect "[^"]+" with timeout/.test(message) && /resolved to [1-9]\d* element/.test(message);
12432
+ return /(?:Expect "[^"]+"|expect\.\w+) with timeout/.test(message) && /resolved to [1-9]\d* element/.test(message);
12310
12433
  }
12311
12434
  function isNetwork(message) {
12312
12435
  return /net::ERR_(CONNECTION|NAME_NOT_RESOLVED|INTERNET_DISCONNECTED|SSL|TIMED_OUT|ADDRESS|NETWORK_CHANGED|EMPTY_RESPONSE)|\bECONN(?:REFUSED|RESET)\b|\bENOTFOUND\b|\bEAI_AGAIN\b|getaddrinfo|socket hang up|fetch failed/.test(errorHeader(message));
@@ -12337,26 +12460,37 @@ function topFrame(stack) {
12337
12460
  function representativeMessage(message) {
12338
12461
  return clampWithEllipsis(message.split("\n")[0], MAX_MESSAGE_CHARS);
12339
12462
  }
12340
- function classifyOne(f, model) {
12463
+ function classifyOne(f, chain) {
12341
12464
  const frame = topFrame(f.error.stack);
12342
12465
  if (f.retryHistory.some((r) => r.status === "passed")) {
12343
- return { category: "flaky-by-retry", margin: OVERRIDE_MARGIN, failure: f, frame };
12466
+ return { category: "flaky-by-retry", margin: OVERRIDE_MARGIN, strength: "strong", source: "retry", failure: f, frame };
12344
12467
  }
12345
12468
  for (const [match, category] of MESSAGE_RULES) {
12346
- if (match(f.error.message)) return { category, margin: OVERRIDE_MARGIN, failure: f, frame };
12469
+ if (match(f.error.message)) {
12470
+ return { category, margin: OVERRIDE_MARGIN, strength: "strong", source: "rule", failure: f, frame };
12471
+ }
12472
+ }
12473
+ if (chain.local) {
12474
+ const { model, acceptThreshold, strongThreshold } = chain.local;
12475
+ const r = classifyTokens(model, localInferenceTokens(f.error.message));
12476
+ if (r.label !== "unknown" && r.margin >= acceptThreshold) {
12477
+ const strength = strongThreshold !== null && r.margin >= strongThreshold ? "strong" : "moderate";
12478
+ return { category: r.label, margin: r.margin, strength, source: "local", failure: f, frame };
12479
+ }
12347
12480
  }
12348
- const { label, margin } = classify(model, classifyInput(f));
12349
- return { category: label, margin, failure: f, frame };
12481
+ const { label, margin } = classify(chain.base, classifyInput(f));
12482
+ return { category: label, margin, strength: strengthOf(margin), source: "base", failure: f, frame };
12350
12483
  }
12351
- function classifyAll(failures, model) {
12352
- return failures.map((f) => classifyOne(f, model));
12484
+ function classifyAll(failures, chain) {
12485
+ return failures.map((f) => classifyOne(f, chain));
12353
12486
  }
12354
12487
  function perTestClassifications(classified) {
12355
12488
  return classified.map((c) => ({
12356
12489
  testId: c.failure.testId,
12357
12490
  category: c.category,
12358
12491
  message: representativeMessage(c.failure.error.message),
12359
- strength: strengthOf(c.margin)
12492
+ strength: c.strength
12493
+ // resolved at classification time, per producing source
12360
12494
  }));
12361
12495
  }
12362
12496
  function clusterClassified(classified) {
@@ -12367,13 +12501,16 @@ function clusterClassified(classified) {
12367
12501
  if (bucket) bucket.push(c);
12368
12502
  else groups.set(key, [c]);
12369
12503
  }
12504
+ const rank = Object.fromEntries(
12505
+ STRENGTH_LEVELS.map((level, i) => [level, i])
12506
+ );
12370
12507
  const clusters = [];
12371
12508
  for (const members of groups.values()) {
12372
- const margin = Math.max(...members.map((m) => m.margin));
12509
+ const strongest = members.reduce((a, b) => rank[b.strength] > rank[a.strength] ? b : a);
12373
12510
  clusters.push({
12374
12511
  category: members[0].category,
12375
- strength: strengthOf(margin),
12376
- margin,
12512
+ strength: strongest.strength,
12513
+ margin: Math.max(...members.map((m) => m.margin)),
12377
12514
  count: members.length,
12378
12515
  tests: members.slice(0, MAX_TESTS_PER_CLUSTER).map((m) => m.failure.testTitle),
12379
12516
  testsTotal: members.length,
@@ -12389,6 +12526,7 @@ var init_FailureClusterer = __esm({
12389
12526
  "use strict";
12390
12527
  init_cjs_shims();
12391
12528
  init_NaiveBayesClassifier();
12529
+ init_redact();
12392
12530
  init_constants();
12393
12531
  init_truncate();
12394
12532
  MAX_TESTS_PER_CLUSTER = 20;
@@ -12404,100 +12542,209 @@ var init_FailureClusterer = __esm({
12404
12542
 
12405
12543
  // src/analysis/feedback-csv.ts
12406
12544
  function parseFeedbackRows(content) {
12407
- return content.trim().split("\n").slice(1).filter(Boolean).map((line) => {
12545
+ const lines = content.trim().split("\n").filter(Boolean);
12546
+ if (lines.length === 0) return [];
12547
+ const header = lines[0].trim();
12548
+ const isV2 = header === FEEDBACK_HEADER;
12549
+ if (!isV2 && header !== LEGACY_FEEDBACK_HEADER) return [];
12550
+ const minParts = isV2 ? 4 : 3;
12551
+ const rows = [];
12552
+ for (const line of lines.slice(1)) {
12408
12553
  const parts = line.split(",");
12409
- return { category: parts[0], margin: parts[1], text: parts.slice(2).join(","), raw: line };
12410
- });
12554
+ if (parts.length < minParts) continue;
12555
+ const row = isV2 ? { category: parts[0], margin: parts[1], label: parts[2], text: parts.slice(3).join(",") } : { category: parts[0], margin: parts[1], label: "", text: parts.slice(2).join(",") };
12556
+ if (row.text.trim().length === 0) continue;
12557
+ rows.push(row);
12558
+ }
12559
+ return rows;
12560
+ }
12561
+ function serializeFeedbackRow(r) {
12562
+ const label = r.label.replace(/[,\r\n]/g, "").trim();
12563
+ return `${r.category},${r.margin},${label},${r.text}`;
12411
12564
  }
12412
12565
  function feedbackIdentity(category, text) {
12413
12566
  return `${category} ${text}`;
12414
12567
  }
12415
- var FEEDBACK_HEADER;
12568
+ function isTrainableLabel(label) {
12569
+ return CATEGORIES.includes(label);
12570
+ }
12571
+ var FEEDBACK_HEADER, LEGACY_FEEDBACK_HEADER;
12416
12572
  var init_feedback_csv = __esm({
12417
12573
  "src/analysis/feedback-csv.ts"() {
12418
12574
  "use strict";
12419
12575
  init_cjs_shims();
12420
- FEEDBACK_HEADER = "category,margin,text";
12576
+ init_constants();
12577
+ FEEDBACK_HEADER = "category,margin,label,text";
12578
+ LEGACY_FEEDBACK_HEADER = "category,margin,text";
12421
12579
  }
12422
12580
  });
12423
12581
 
12424
- // src/analysis/UnclassifiedCollector.ts
12425
- function redactForStorage(raw) {
12426
- 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 ");
12427
- return tokenize(redacted).join(" ");
12582
+ // src/analysis/FeedbackStore.ts
12583
+ function readAllFeedback(home = import_node_os3.default.homedir()) {
12584
+ const root = import_node_path3.default.join(home, ".reportforge");
12585
+ if (!import_node_fs3.default.existsSync(root)) return [];
12586
+ const out = [];
12587
+ for (const entry of import_node_fs3.default.readdirSync(root).sort()) {
12588
+ const dir = import_node_path3.default.join(root, entry);
12589
+ const file = import_node_path3.default.join(dir, FILENAME);
12590
+ if (!import_node_fs3.default.existsSync(file)) continue;
12591
+ out.push({
12592
+ file,
12593
+ rows: parseFeedbackRows(import_node_fs3.default.readFileSync(file, "utf8")),
12594
+ project: readProjectMarker(dir)
12595
+ });
12596
+ }
12597
+ return out;
12598
+ }
12599
+ function readProjectMarker(projectDir) {
12600
+ try {
12601
+ const parsed = JSON.parse(import_node_fs3.default.readFileSync(import_node_path3.default.join(projectDir, MARKER_FILENAME), "utf8"));
12602
+ return typeof parsed?.cwd === "string" ? parsed : null;
12603
+ } catch {
12604
+ return null;
12605
+ }
12606
+ }
12607
+ function writeProjectMarker(projectDir, marker) {
12608
+ try {
12609
+ atomicWrite(import_node_path3.default.join(projectDir, MARKER_FILENAME), JSON.stringify(marker));
12610
+ } catch {
12611
+ }
12612
+ }
12613
+ function labeledSamples(files) {
12614
+ const samples = [];
12615
+ let invalidLabelCount = 0;
12616
+ let unlabeledCount = 0;
12617
+ for (const f of files) {
12618
+ for (const r of f.rows) {
12619
+ if (r.label === "") {
12620
+ unlabeledCount++;
12621
+ continue;
12622
+ }
12623
+ if (!isTrainableLabel(r.label)) {
12624
+ invalidLabelCount++;
12625
+ continue;
12626
+ }
12627
+ const tokens = r.text.split(" ").filter(Boolean);
12628
+ if (tokens.length === 0) continue;
12629
+ samples.push({ label: r.label, tokens });
12630
+ }
12631
+ }
12632
+ return { samples, invalidLabelCount, unlabeledCount };
12633
+ }
12634
+ function rewriteFeedbackFile(file, rows) {
12635
+ const body = rows.map(serializeFeedbackRow).join("\n");
12636
+ atomicWrite(file, `${FEEDBACK_HEADER}
12637
+ ${body}${body ? "\n" : ""}`);
12428
12638
  }
12429
- function shouldCollect(c) {
12430
- return c.category === "unknown" || c.margin <= STRENGTH.collectMaxMargin;
12639
+ function atomicWrite(file, content) {
12640
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
12641
+ const tmp = `${file}.${process.pid}.tmp`;
12642
+ import_node_fs3.default.writeFileSync(tmp, content, "utf8");
12643
+ import_node_fs3.default.renameSync(tmp, file);
12644
+ }
12645
+ var import_node_fs3, import_node_os3, import_node_path3, FILENAME, MARKER_FILENAME;
12646
+ var init_FeedbackStore = __esm({
12647
+ "src/analysis/FeedbackStore.ts"() {
12648
+ "use strict";
12649
+ init_cjs_shims();
12650
+ import_node_fs3 = __toESM(require("fs"));
12651
+ import_node_os3 = __toESM(require("os"));
12652
+ import_node_path3 = __toESM(require("path"));
12653
+ init_feedback_csv();
12654
+ FILENAME = "unclassified.csv";
12655
+ MARKER_FILENAME = "project.json";
12656
+ }
12657
+ });
12658
+
12659
+ // src/analysis/UnclassifiedCollector.ts
12660
+ function shouldCollect(c, scope) {
12661
+ if (c.source !== "base") return false;
12662
+ if (c.category === "unknown" || c.margin <= STRENGTH.collectMaxMargin) return true;
12663
+ return scope === "all";
12431
12664
  }
12432
12665
  function tildeify(p) {
12433
- const home = import_node_os2.default.homedir();
12666
+ const home = import_node_os4.default.homedir();
12434
12667
  return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
12435
12668
  }
12436
- function collectUnclassified(classified, projectDir) {
12437
- 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);
12669
+ function collectUnclassified(classified, projectDir, opts = {}) {
12670
+ const scope = opts.scope ?? "blind-spots";
12671
+ const rows = classified.filter((c) => shouldCollect(c, scope)).map((c) => ({ text: redactForStorage(c.failure.error.message), category: c.category, margin: c.margin })).filter((r) => r.text.length > 0);
12438
12672
  if (rows.length === 0) return;
12439
- const file = import_node_path2.default.join(projectDir, FILENAME);
12440
- const firstTime = !import_node_fs2.default.existsSync(file);
12673
+ const file = import_node_path4.default.join(projectDir, FILENAME2);
12674
+ const firstTime = !import_node_fs4.default.existsSync(file);
12441
12675
  const byIdentity = /* @__PURE__ */ new Map();
12442
12676
  if (!firstTime) {
12443
- for (const r of parseFeedbackRows(import_node_fs2.default.readFileSync(file, "utf8"))) {
12444
- byIdentity.set(feedbackIdentity(r.category, r.text), r.raw);
12677
+ for (const r of parseFeedbackRows(import_node_fs4.default.readFileSync(file, "utf8"))) {
12678
+ byIdentity.set(feedbackIdentity(r.category, r.text), r);
12445
12679
  }
12446
12680
  }
12447
- let added = false;
12448
12681
  for (const r of rows) {
12449
12682
  const id = feedbackIdentity(r.category, r.text);
12683
+ const label = byIdentity.get(id)?.label ?? "";
12450
12684
  byIdentity.delete(id);
12451
- byIdentity.set(id, `${r.category},${r.margin},${r.text}`);
12452
- added = true;
12453
- }
12454
- if (!added) return;
12455
- const all = [...byIdentity.values()].slice(-MAX_ROWS);
12456
- import_node_fs2.default.mkdirSync(projectDir, { recursive: true });
12457
- import_node_fs2.default.writeFileSync(file, `${FEEDBACK_HEADER}
12458
- ${all.join("\n")}
12459
- `, "utf8");
12685
+ byIdentity.set(id, { category: r.category, margin: String(r.margin), label, text: r.text });
12686
+ }
12687
+ const all = evictOldestUnlabeledFirst([...byIdentity.values()], MAX_ROWS);
12688
+ import_node_fs4.default.mkdirSync(projectDir, { recursive: true });
12689
+ rewriteFeedbackFile(file, all);
12690
+ if (opts.project) {
12691
+ writeProjectMarker(projectDir, { cwd: opts.project.cwd, outputFile: opts.project.outputFile, updatedAt: Date.now() });
12692
+ }
12460
12693
  if (firstTime) {
12461
12694
  logger.info(
12462
12695
  `Unrecognized failures collected locally at ${tildeify(file)} to improve offline classification. Local only - never sent anywhere. Disable: failureAnalysis.collectUnclassified=false`
12463
12696
  );
12464
12697
  }
12465
12698
  }
12466
- var import_node_fs2, import_node_os2, import_node_path2, MAX_ROWS, FILENAME, URI_RE, EMAIL_RE, SECRET_RE, IP_RE, WIN_PATH_RE, UNIX_PATH_RE;
12699
+ function evictOldestUnlabeledFirst(rows, max) {
12700
+ const excess = rows.length - max;
12701
+ if (excess <= 0) return rows;
12702
+ let unlabeledToDrop = Math.min(excess, rows.filter((r) => r.label === "").length);
12703
+ let labeledToDrop = excess - unlabeledToDrop;
12704
+ const out = [];
12705
+ for (const r of rows) {
12706
+ if (r.label === "" && unlabeledToDrop > 0) {
12707
+ unlabeledToDrop--;
12708
+ continue;
12709
+ }
12710
+ if (r.label !== "" && labeledToDrop > 0) {
12711
+ labeledToDrop--;
12712
+ continue;
12713
+ }
12714
+ out.push(r);
12715
+ }
12716
+ return out;
12717
+ }
12718
+ var import_node_fs4, import_node_os4, import_node_path4, MAX_ROWS, FILENAME2;
12467
12719
  var init_UnclassifiedCollector = __esm({
12468
12720
  "src/analysis/UnclassifiedCollector.ts"() {
12469
12721
  "use strict";
12470
12722
  init_cjs_shims();
12471
- import_node_fs2 = __toESM(require("fs"));
12472
- import_node_os2 = __toESM(require("os"));
12473
- import_node_path2 = __toESM(require("path"));
12474
- init_tokenize();
12723
+ import_node_fs4 = __toESM(require("fs"));
12724
+ import_node_os4 = __toESM(require("os"));
12725
+ import_node_path4 = __toESM(require("path"));
12475
12726
  init_constants();
12476
12727
  init_feedback_csv();
12728
+ init_redact();
12729
+ init_FeedbackStore();
12477
12730
  init_logger();
12478
12731
  MAX_ROWS = 500;
12479
- FILENAME = "unclassified.csv";
12480
- URI_RE = /[a-z][\w+.-]*:\/\/\S+/gi;
12481
- EMAIL_RE = /\b[\w.+-]+@[\w.-]+\.\w+\b/g;
12482
- SECRET_RE = /\b(?=[A-Za-z0-9_+/=-]*[0-9])(?=[A-Za-z0-9_+/=-]*[A-Za-z])[A-Za-z0-9_+/=-]{16,}\b/g;
12483
- IP_RE = /\b\d{1,3}(?:\.\d{1,3}){3}\b/g;
12484
- WIN_PATH_RE = /[A-Za-z]:\\[^\s'"]+/g;
12485
- UNIX_PATH_RE = /(?:\/[\w.-]+){2,}/g;
12732
+ FILENAME2 = "unclassified.csv";
12486
12733
  }
12487
12734
  });
12488
12735
 
12489
12736
  // src/utils/paths.ts
12490
12737
  function resolveProjectDir(options, cwd) {
12491
12738
  const projectKey = import_node_crypto2.default.createHash("sha256").update(cwd + (options.outputFile ?? "")).digest("hex").slice(0, 8);
12492
- return import_node_path3.default.join(import_node_os3.default.homedir(), ".reportforge", projectKey);
12739
+ return import_node_path5.default.join(import_node_os5.default.homedir(), ".reportforge", projectKey);
12493
12740
  }
12494
- var import_node_os3, import_node_path3, import_node_crypto2;
12741
+ var import_node_os5, import_node_path5, import_node_crypto2;
12495
12742
  var init_paths = __esm({
12496
12743
  "src/utils/paths.ts"() {
12497
12744
  "use strict";
12498
12745
  init_cjs_shims();
12499
- import_node_os3 = __toESM(require("os"));
12500
- import_node_path3 = __toESM(require("path"));
12746
+ import_node_os5 = __toESM(require("os"));
12747
+ import_node_path5 = __toESM(require("path"));
12501
12748
  import_node_crypto2 = __toESM(require("crypto"));
12502
12749
  }
12503
12750
  });
@@ -12530,27 +12777,49 @@ function runFailureAnalysis(reportData, options, cwd) {
12530
12777
  if (reportData.stats.failed === 0 || !fa.enabled) return;
12531
12778
  const cap = fa.maxFailuresToAnalyse;
12532
12779
  const analysed = reportData.failures.slice(0, cap);
12533
- const model = loadModel(fa.autoUpdateModel);
12534
- const classified = classifyAll(analysed, model);
12780
+ const base = loadModel(fa.autoUpdateModel);
12781
+ const local = fa.localModel ? loadLocalModel(fa.localModelPath) ?? void 0 : void 0;
12782
+ if (local) {
12783
+ logger.debug(
12784
+ `local model active (trained ${local.meta.trainedAt}, ${local.meta.labeledCount} labeled rows, accept margin >= ${local.acceptThreshold})`
12785
+ );
12786
+ if (local.meta.baseVersionAtEval !== base.version) {
12787
+ logger.debug(
12788
+ `local model was gate-checked against base v${local.meta.baseVersionAtEval} but base v${base.version} is active - re-run train-model to re-validate against the current base`
12789
+ );
12790
+ }
12791
+ }
12792
+ const classified = classifyAll(analysed, { base, local });
12793
+ logger.debug(`failure analysis: ${classified.length} classified - ${sourceTally(classified)}`);
12535
12794
  reportData.classifications = perTestClassifications(classified);
12536
12795
  if (templatesConsumeAnalysis(options)) {
12537
12796
  reportData.analysis = analyseClassified(classified, reportData.failures.length, fa);
12538
12797
  }
12539
12798
  if (fa.collectUnclassified) {
12540
- collectUnclassified(classified, resolveProjectDir(options, cwd));
12799
+ collectUnclassified(classified, resolveProjectDir(options, cwd), {
12800
+ scope: fa.collectScope,
12801
+ project: { cwd, outputFile: options.outputFile }
12802
+ });
12541
12803
  }
12542
12804
  }
12805
+ function sourceTally(classified) {
12806
+ const counts = /* @__PURE__ */ new Map();
12807
+ for (const c of classified) counts.set(c.source, (counts.get(c.source) ?? 0) + 1);
12808
+ return ["rule", "retry", "local", "base"].filter((s) => counts.has(s)).map((s) => `${counts.get(s)} ${s}`).join(" / ");
12809
+ }
12543
12810
  var DEFAULTS, STRENGTH_RANK;
12544
12811
  var init_analysis = __esm({
12545
12812
  "src/analysis/index.ts"() {
12546
12813
  "use strict";
12547
12814
  init_cjs_shims();
12548
12815
  init_ModelStore();
12816
+ init_LocalModelStore();
12549
12817
  init_FailureClusterer();
12550
12818
  init_SummaryWriter();
12551
12819
  init_constants();
12552
12820
  init_UnclassifiedCollector();
12553
12821
  init_paths();
12822
+ init_logger();
12554
12823
  DEFAULTS = { maxClusters: 10, minStrength: "weak", maxFailuresToAnalyse: 500 };
12555
12824
  STRENGTH_RANK = Object.fromEntries(
12556
12825
  STRENGTH_LEVELS.map((level, i) => [level, i])
@@ -12582,7 +12851,7 @@ async function run() {
12582
12851
  console.log(` Template : ${template}`);
12583
12852
  console.log(` Output : ${outputFile}`);
12584
12853
  const reportData = buildDemoReportData(template);
12585
- const options = parseOptions({ template, outputFile, failureAnalysis: { collectUnclassified: false } });
12854
+ const options = parseOptions({ template, outputFile, failureAnalysis: { collectUnclassified: false, localModel: false } });
12586
12855
  const generator = new PdfGenerator();
12587
12856
  runFailureAnalysis(reportData, options, process.cwd());
12588
12857
  reportData.briefSentence = buildBriefSentence(reportData.stats, reportData.analysis, reportData.charts.trend);
@@ -12646,22 +12915,12 @@ __export(export_feedback_exports, {
12646
12915
  exportFeedbackCli: () => exportFeedbackCli
12647
12916
  });
12648
12917
  function exportFeedback(deps = {}) {
12649
- const home = deps.home ?? import_node_os4.default.homedir();
12918
+ const home = deps.home ?? import_node_os6.default.homedir();
12650
12919
  const cwd = deps.cwd ?? process.cwd();
12651
- const root = import_node_path4.default.join(home, ".reportforge");
12652
- const rows = [];
12653
- if (import_node_fs3.default.existsSync(root)) {
12654
- for (const entry of import_node_fs3.default.readdirSync(root)) {
12655
- const file = import_node_path4.default.join(root, entry, "unclassified.csv");
12656
- if (!import_node_fs3.default.existsSync(file)) continue;
12657
- for (const r of parseFeedbackRows(import_node_fs3.default.readFileSync(file, "utf8"))) {
12658
- rows.push(r.raw);
12659
- }
12660
- }
12661
- }
12920
+ const rows = readAllFeedback(home).flatMap((f) => f.rows.map(serializeFeedbackRow));
12662
12921
  if (rows.length === 0) return { rowCount: 0, outFile: null };
12663
- const outFile = import_node_path4.default.join(cwd, "reportforge-feedback.csv");
12664
- import_node_fs3.default.writeFileSync(outFile, `${FEEDBACK_HEADER}
12922
+ const outFile = import_node_path6.default.join(cwd, "reportforge-feedback.csv");
12923
+ import_node_fs5.default.writeFileSync(outFile, `${FEEDBACK_HEADER}
12665
12924
  ${rows.join("\n")}
12666
12925
  `, "utf8");
12667
12926
  return { rowCount: rows.length, outFile };
@@ -12678,15 +12937,558 @@ Review it before sharing; there is no upload.
12678
12937
  );
12679
12938
  }
12680
12939
  }
12681
- var import_node_fs3, import_node_os4, import_node_path4;
12940
+ var import_node_fs5, import_node_os6, import_node_path6;
12682
12941
  var init_export_feedback = __esm({
12683
12942
  "src/cli/export-feedback.ts"() {
12684
12943
  "use strict";
12685
12944
  init_cjs_shims();
12686
- import_node_fs3 = __toESM(require("fs"));
12687
- import_node_os4 = __toESM(require("os"));
12688
- import_node_path4 = __toESM(require("path"));
12945
+ import_node_fs5 = __toESM(require("fs"));
12946
+ import_node_os6 = __toESM(require("os"));
12947
+ import_node_path6 = __toESM(require("path"));
12689
12948
  init_feedback_csv();
12949
+ init_FeedbackStore();
12950
+ }
12951
+ });
12952
+
12953
+ // src/cli/label-feedback.ts
12954
+ var label_feedback_exports = {};
12955
+ __export(label_feedback_exports, {
12956
+ labelFeedback: () => labelFeedback,
12957
+ labelFeedbackCli: () => labelFeedbackCli
12958
+ });
12959
+ async function labelFeedback(deps) {
12960
+ const { io } = deps;
12961
+ if (!io.isTTY) {
12962
+ io.write(`${nonInteractiveHelp()}
12963
+ `);
12964
+ return { labeled: 0, skipped: 0, remaining: countUnlabeled(deps.home), nonInteractive: true };
12965
+ }
12966
+ const files = readAllFeedback(deps.home ?? import_node_os7.default.homedir());
12967
+ const menu = CATEGORIES.map((c, i) => ` ${i + 1}) ${c}`).join("\n");
12968
+ let labeled = 0;
12969
+ let skipped = 0;
12970
+ let quit = false;
12971
+ io.write(`${CONVENTIONS}
12972
+
12973
+ `);
12974
+ for (const f of files) {
12975
+ if (quit) break;
12976
+ for (let i = 0; i < f.rows.length && !quit; i++) {
12977
+ const row = f.rows[i];
12978
+ if (row.label !== "") continue;
12979
+ io.write(
12980
+ `
12981
+ project: ${f.project?.cwd ?? "(unknown - marker written on next run)"}
12982
+ guess: ${row.category} @ margin ${row.margin}
12983
+ tokens: ${row.text}
12984
+ ${menu}
12985
+ s) skip q) quit
12986
+ `
12987
+ );
12988
+ for (; ; ) {
12989
+ const answer = (await io.question("label> ")).trim().toLowerCase();
12990
+ if (answer === "q") {
12991
+ quit = true;
12992
+ break;
12993
+ }
12994
+ if (answer === "s") {
12995
+ skipped++;
12996
+ break;
12997
+ }
12998
+ const idx = Number.parseInt(answer, 10) - 1;
12999
+ if (Number.isInteger(idx) && idx >= 0 && idx < CATEGORIES.length) {
13000
+ row.label = CATEGORIES[idx];
13001
+ rewriteFeedbackFile(f.file, f.rows);
13002
+ labeled++;
13003
+ break;
13004
+ }
13005
+ io.write(` enter 1-${CATEGORIES.length}, s, or q
13006
+ `);
13007
+ }
13008
+ }
13009
+ }
13010
+ const remaining = files.flatMap((f) => f.rows).filter((r) => r.label === "").length;
13011
+ io.write(
13012
+ `
13013
+ labeled ${labeled}, skipped ${skipped}, ${remaining} still unlabeled.
13014
+ ` + (labeled > 0 ? "next: npx @reportforge/playwright-pdf train-model\n" : "")
13015
+ );
13016
+ return { labeled, skipped, remaining, nonInteractive: false };
13017
+ }
13018
+ function countUnlabeled(home) {
13019
+ return readAllFeedback(home ?? import_node_os7.default.homedir()).flatMap((f) => f.rows).filter((r) => r.label === "").length;
13020
+ }
13021
+ async function labelFeedbackCli() {
13022
+ const { createInterface } = await import("readline/promises");
13023
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
13024
+ const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
13025
+ const io = {
13026
+ question: (p) => rl.question(p),
13027
+ // Interactive chatter → stdout; the non-interactive help is an ERROR message → stderr.
13028
+ write: (s) => void (isTTY ? process.stdout : process.stderr).write(s),
13029
+ isTTY
13030
+ };
13031
+ try {
13032
+ const res = await labelFeedback({ io });
13033
+ if (res.nonInteractive) process.exitCode = 1;
13034
+ } finally {
13035
+ rl.close();
13036
+ }
13037
+ }
13038
+ var import_node_os7, CONVENTIONS, nonInteractiveHelp;
13039
+ var init_label_feedback = __esm({
13040
+ "src/cli/label-feedback.ts"() {
13041
+ "use strict";
13042
+ init_cjs_shims();
13043
+ import_node_os7 = __toESM(require("os"));
13044
+ init_FeedbackStore();
13045
+ init_constants();
13046
+ CONVENTIONS = [
13047
+ "Labeling conventions:",
13048
+ ' - a timed-out page.goto / "Navigation timeout exceeded" is a TIMEOUT (navigation = aborted/interrupted/crashed, not slow)',
13049
+ ' - "Test timeout ... waiting for locator" is a TIMEOUT when the element existed but the test clock ran out,',
13050
+ " and LOCATOR-NOT-FOUND when the element demonstrably never existed (resolved to 0 / no such element)",
13051
+ " - skip anything you are not sure about - a wrong label is worse than no label"
13052
+ ].join("\n");
13053
+ nonInteractiveHelp = () => [
13054
+ "label-feedback needs an interactive terminal (TTY) - run it directly, not through a pipe or CI.",
13055
+ `Hand-edit alternative: open ~/.reportforge/<project>/unclassified.csv and fill the empty 'label'`,
13056
+ `column with one of: ${CATEGORIES.join(" | ")}`,
13057
+ "Then run: npx @reportforge/playwright-pdf train-model"
13058
+ ].join("\n");
13059
+ }
13060
+ });
13061
+
13062
+ // src/analysis/LocalTrainer.ts
13063
+ function trainNaiveBayes(samples, alpha = 1) {
13064
+ if (samples.length === 0) throw new Error("trainNaiveBayes: no samples");
13065
+ const labels = [...new Set(samples.map((s) => s.label))].sort();
13066
+ const counts = new Map(labels.map((l) => [l, /* @__PURE__ */ new Map()]));
13067
+ const totals = Object.fromEntries(labels.map((l) => [l, 0]));
13068
+ const labelDocs = Object.fromEntries(labels.map((l) => [l, 0]));
13069
+ const vocab = /* @__PURE__ */ new Set();
13070
+ for (const { label, tokens } of samples) {
13071
+ labelDocs[label]++;
13072
+ const c = counts.get(label);
13073
+ for (const tok of tokens) {
13074
+ c.set(tok, (c.get(tok) ?? 0) + 1);
13075
+ totals[label]++;
13076
+ vocab.add(tok);
13077
+ }
13078
+ }
13079
+ const n = samples.length;
13080
+ const vocabSize = vocab.size;
13081
+ const priors = {};
13082
+ const logLikelihoods = {};
13083
+ for (const l of labels) {
13084
+ priors[l] = Math.log(labelDocs[l] / n);
13085
+ const ll = {};
13086
+ for (const [tok, c] of counts.get(l)) {
13087
+ ll[tok] = Math.log((c + alpha) / (totals[l] + alpha * vocabSize));
13088
+ }
13089
+ logLikelihoods[l] = ll;
13090
+ }
13091
+ return {
13092
+ labels,
13093
+ priors,
13094
+ logLikelihoods,
13095
+ totals,
13096
+ vocabSize,
13097
+ alpha,
13098
+ version: 0,
13099
+ playwrightVersion: "local"
13100
+ };
13101
+ }
13102
+ var init_LocalTrainer = __esm({
13103
+ "src/analysis/LocalTrainer.ts"() {
13104
+ "use strict";
13105
+ init_cjs_shims();
13106
+ }
13107
+ });
13108
+
13109
+ // src/analysis/LocalEvaluator.ts
13110
+ function evaluateChain(samples, base, opts = {}) {
13111
+ const seed = opts.seed ?? LOCAL_TRAINING.seed;
13112
+ const method = samples.length < LOCAL_TRAINING.cvSwitchoverN ? "loocv" : "stratified-5fold";
13113
+ const folds = method === "loocv" ? samples.map((_, i) => i) : stratifiedFolds(samples, LOCAL_TRAINING.folds, seed);
13114
+ const foldCount = method === "loocv" ? samples.length : LOCAL_TRAINING.folds;
13115
+ const oof = samples.map((s) => ({
13116
+ trueLabel: s.label,
13117
+ base: pick(classifyTokens(base, s.tokens)),
13118
+ local: { label: "unknown", margin: 0 }
13119
+ // filled below
13120
+ }));
13121
+ for (let k = 0; k < foldCount; k++) {
13122
+ const trainRows = samples.filter((_, i) => folds[i] !== k);
13123
+ if (trainRows.length === 0) continue;
13124
+ const foldModel = trainNaiveBayes(trainRows);
13125
+ samples.forEach((s, i) => {
13126
+ if (folds[i] === k) oof[i].local = pick(classifyTokens(foldModel, s.tokens));
13127
+ });
13128
+ }
13129
+ const reasons = [];
13130
+ const acceptThreshold = calibrate(oof, LOCAL_TRAINING.acceptPrecision);
13131
+ const strongThreshold = acceptThreshold === null ? null : calibrate(oof, LOCAL_TRAINING.strongPrecision, acceptThreshold);
13132
+ const classes = [...new Set(samples.map((s) => s.label))].sort();
13133
+ const baseMetrics = computeMetrics(oof.map((p) => ({ trueLabel: p.trueLabel, pred: p.base.label })), classes);
13134
+ const chainMetrics = computeMetrics(
13135
+ oof.map((p) => ({
13136
+ trueLabel: p.trueLabel,
13137
+ pred: acceptThreshold !== null && p.local.label !== "unknown" && p.local.margin >= acceptThreshold ? p.local.label : p.base.label
13138
+ })),
13139
+ classes
13140
+ );
13141
+ let gatePassed = false;
13142
+ if (acceptThreshold === null) {
13143
+ reasons.push(
13144
+ `gate FAILED: no margin threshold reaches ${LOCAL_TRAINING.acceptPrecision * 100}% precision with >= ${LOCAL_TRAINING.minAccepted} accepted held-out predictions - the local model is never confident enough to accept safely`
13145
+ );
13146
+ } else if (chainMetrics.macroF1 <= baseMetrics.macroF1) {
13147
+ reasons.push(
13148
+ `gate FAILED: chain macro-F1 ${fmt(chainMetrics.macroF1)} does not beat base ${fmt(baseMetrics.macroF1)} (tie keeps the champion)`
13149
+ );
13150
+ } else {
13151
+ gatePassed = true;
13152
+ reasons.push(
13153
+ `gate passed: chain macro-F1 ${fmt(chainMetrics.macroF1)} > base ${fmt(baseMetrics.macroF1)}; the local layer answers only above its calibrated margin threshold` + (strongThreshold !== null ? "" : " and never renders 'strong'")
13154
+ );
13155
+ }
13156
+ return {
13157
+ method,
13158
+ n: samples.length,
13159
+ classes,
13160
+ base: baseMetrics,
13161
+ chain: chainMetrics,
13162
+ acceptThreshold,
13163
+ strongThreshold,
13164
+ gatePassed,
13165
+ reasons,
13166
+ oofLocal: oof.map((p) => p.local)
13167
+ };
13168
+ }
13169
+ function transferThresholds(result, samples, finalModel) {
13170
+ const finalMarginsOver = (oofThreshold) => result.oofLocal.map((p, i) => ({ p, i })).filter(({ p }) => p.label !== "unknown" && p.margin >= oofThreshold).map(({ i }) => classifyTokens(finalModel, samples[i].tokens)).filter((r) => r.label !== "unknown").map((r) => r.margin);
13171
+ if (result.acceptThreshold === null) return { acceptThreshold: null, strongThreshold: null };
13172
+ const accept = finalMarginsOver(result.acceptThreshold);
13173
+ if (accept.length === 0) return { acceptThreshold: null, strongThreshold: null };
13174
+ const acceptThreshold = Math.min(...accept);
13175
+ if (result.strongThreshold === null) return { acceptThreshold, strongThreshold: null };
13176
+ const strong = finalMarginsOver(result.strongThreshold);
13177
+ return {
13178
+ acceptThreshold,
13179
+ // 'strong' must never be easier to reach than acceptance itself.
13180
+ strongThreshold: strong.length === 0 ? null : Math.max(Math.min(...strong), acceptThreshold)
13181
+ };
13182
+ }
13183
+ function calibrate(oof, target, atLeast = -Infinity) {
13184
+ const scored = oof.filter((p) => p.local.label !== "unknown");
13185
+ const candidates = [...new Set(scored.map((p) => p.local.margin))].filter((t) => t >= atLeast).sort((a, b) => a - b);
13186
+ for (const t of candidates) {
13187
+ const accepted = scored.filter((p) => p.local.margin >= t);
13188
+ if (accepted.length < LOCAL_TRAINING.minAccepted) break;
13189
+ const correct = accepted.filter((p) => p.local.label === p.trueLabel).length;
13190
+ if (correct / accepted.length >= target) return t;
13191
+ }
13192
+ return null;
13193
+ }
13194
+ function computeMetrics(preds, classes) {
13195
+ var _a;
13196
+ const confusion = {};
13197
+ for (const p of preds) {
13198
+ (confusion[_a = p.trueLabel] ?? (confusion[_a] = {}))[p.pred] = (confusion[p.trueLabel][p.pred] ?? 0) + 1;
13199
+ }
13200
+ const perClass = {};
13201
+ let f1Sum = 0;
13202
+ for (const c of classes) {
13203
+ const tp = preds.filter((p) => p.trueLabel === c && p.pred === c).length;
13204
+ const fp = preds.filter((p) => p.trueLabel !== c && p.pred === c).length;
13205
+ const fn = preds.filter((p) => p.trueLabel === c && p.pred !== c).length;
13206
+ const precision = tp + fp === 0 ? 0 : tp / (tp + fp);
13207
+ const recall = tp + fn === 0 ? 0 : tp / (tp + fn);
13208
+ const f1 = precision + recall === 0 ? 0 : 2 * precision * recall / (precision + recall);
13209
+ perClass[c] = { precision, recall, f1, support: tp + fn };
13210
+ f1Sum += f1;
13211
+ }
13212
+ return {
13213
+ accuracy: preds.length === 0 ? 0 : preds.filter((p) => p.pred === p.trueLabel).length / preds.length,
13214
+ macroF1: classes.length === 0 ? 0 : f1Sum / classes.length,
13215
+ perClass,
13216
+ confusion
13217
+ };
13218
+ }
13219
+ function stratifiedFolds(samples, folds, seed) {
13220
+ const rng = mulberry32(seed);
13221
+ const byLabel = /* @__PURE__ */ new Map();
13222
+ samples.forEach((s, i) => {
13223
+ const arr = byLabel.get(s.label);
13224
+ if (arr) arr.push(i);
13225
+ else byLabel.set(s.label, [i]);
13226
+ });
13227
+ const assignment = new Array(samples.length);
13228
+ for (const label of [...byLabel.keys()].sort()) {
13229
+ const indices = shuffle(byLabel.get(label), rng);
13230
+ indices.forEach((rowIdx, j) => {
13231
+ assignment[rowIdx] = j % folds;
13232
+ });
13233
+ }
13234
+ return assignment;
13235
+ }
13236
+ function shuffle(arr, rng) {
13237
+ const a = [...arr];
13238
+ for (let i = a.length - 1; i > 0; i--) {
13239
+ const j = Math.floor(rng() * (i + 1));
13240
+ [a[i], a[j]] = [a[j], a[i]];
13241
+ }
13242
+ return a;
13243
+ }
13244
+ function mulberry32(seed) {
13245
+ let a = seed >>> 0;
13246
+ return () => {
13247
+ a |= 0;
13248
+ a = a + 1831565813 | 0;
13249
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
13250
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
13251
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
13252
+ };
13253
+ }
13254
+ var pick, fmt;
13255
+ var init_LocalEvaluator = __esm({
13256
+ "src/analysis/LocalEvaluator.ts"() {
13257
+ "use strict";
13258
+ init_cjs_shims();
13259
+ init_NaiveBayesClassifier();
13260
+ init_LocalTrainer();
13261
+ init_constants();
13262
+ pick = (r) => ({ label: r.label, margin: r.margin });
13263
+ fmt = (x) => x.toFixed(3);
13264
+ }
13265
+ });
13266
+
13267
+ // src/cli/model-cli-shared.ts
13268
+ function gatherSamples(deps = {}) {
13269
+ const home = deps.home ?? import_node_os8.default.homedir();
13270
+ const cwd = deps.cwd ?? process.cwd();
13271
+ let files = readAllFeedback(home);
13272
+ const warnings = [];
13273
+ if (deps.projectOnly) {
13274
+ const unmarked = files.filter((f) => f.project === null).length;
13275
+ files = files.filter((f) => f.project?.cwd === cwd);
13276
+ if (unmarked > 0) {
13277
+ warnings.push(
13278
+ `${unmarked} feedback dir(s) have no project marker yet (written on the next reporter run) - --project cannot match them`
13279
+ );
13280
+ }
13281
+ }
13282
+ const { samples, invalidLabelCount, unlabeledCount } = labeledSamples(files);
13283
+ if (invalidLabelCount > 0) {
13284
+ warnings.push(
13285
+ `${invalidLabelCount} row(s) have unrecognized labels and were skipped (valid: ${CATEGORIES.join(", ")})`
13286
+ );
13287
+ }
13288
+ if (unlabeledCount > 0) {
13289
+ warnings.push(`${unlabeledCount} collected row(s) are still unlabeled - label them via label-feedback`);
13290
+ }
13291
+ const classes = new Set(samples.map((s) => s.label));
13292
+ let refusal = null;
13293
+ if (samples.length < LOCAL_TRAINING.minLabeled || classes.size < LOCAL_TRAINING.minClasses) {
13294
+ const tally = [...classes].sort().map((c) => `${c}: ${samples.filter((s) => s.label === c).length}`).join(", ") || "none";
13295
+ refusal = `not enough labeled feedback to train safely: ${samples.length} labeled row(s) across ${classes.size} class(es) (${tally}). Need >= ${LOCAL_TRAINING.minLabeled} rows across >= ${LOCAL_TRAINING.minClasses} classes - run your tests, then label collected failures via: npx @reportforge/playwright-pdf label-feedback`;
13296
+ }
13297
+ return { samples, files, warnings, refusal };
13298
+ }
13299
+ function formatEvalReport(r, baseVersion) {
13300
+ const lines = [];
13301
+ lines.push(`Evaluation: ${r.method} over ${r.n} labeled row(s), ${r.classes.length} class(es) [base model v${baseVersion}]`);
13302
+ lines.push("");
13303
+ lines.push(pad(["arm", "accuracy", "macro-F1"], [10, 10, 10]));
13304
+ lines.push(pad(["base", pct(r.base.accuracy), num(r.base.macroF1)], [10, 10, 10]));
13305
+ lines.push(pad(["chain", pct(r.chain.accuracy), num(r.chain.macroF1)], [10, 10, 10]));
13306
+ lines.push("");
13307
+ lines.push(pad(["class", "support", "base F1", "chain F1", "chain P", "chain R"], [20, 8, 9, 9, 8, 8]));
13308
+ for (const c of r.classes) {
13309
+ const b = r.base.perClass[c];
13310
+ const ch = r.chain.perClass[c];
13311
+ lines.push(pad([c, String(ch.support), num(b.f1), num(ch.f1), num(ch.precision), num(ch.recall)], [20, 8, 9, 9, 8, 8]));
13312
+ }
13313
+ lines.push("");
13314
+ lines.push(formatConfusion(r.chain, "chain confusion (true -> predicted)"));
13315
+ lines.push("");
13316
+ lines.push(
13317
+ `accept threshold: ${r.acceptThreshold === null ? "none reachable" : num(r.acceptThreshold)} strong threshold: ${r.strongThreshold === null ? "n/a" : num(r.strongThreshold)}`
13318
+ );
13319
+ for (const reason of r.reasons) lines.push(reason);
13320
+ return lines.join("\n");
13321
+ }
13322
+ function formatConfusion(m, title) {
13323
+ const predicted = [...new Set(Object.values(m.confusion).flatMap((row) => Object.keys(row)))].sort();
13324
+ const lines = [title, pad(["true \\ pred", ...predicted], [20, ...predicted.map(() => 12)])];
13325
+ for (const t of Object.keys(m.confusion).sort()) {
13326
+ lines.push(pad([t, ...predicted.map((p) => String(m.confusion[t][p] ?? 0))], [20, ...predicted.map(() => 12)]));
13327
+ }
13328
+ return lines.join("\n");
13329
+ }
13330
+ function pad(cells, widths) {
13331
+ return cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
13332
+ }
13333
+ function flagValue(argv, flag) {
13334
+ const hit = argv.find((a) => a.startsWith(`${flag}=`));
13335
+ return hit?.slice(flag.length + 1);
13336
+ }
13337
+ function hasFlag(argv, flag) {
13338
+ return argv.includes(flag);
13339
+ }
13340
+ var import_node_os8, pct, num;
13341
+ var init_model_cli_shared = __esm({
13342
+ "src/cli/model-cli-shared.ts"() {
13343
+ "use strict";
13344
+ init_cjs_shims();
13345
+ import_node_os8 = __toESM(require("os"));
13346
+ init_FeedbackStore();
13347
+ init_constants();
13348
+ pct = (x) => `${(x * 100).toFixed(1)}%`;
13349
+ num = (x) => x.toFixed(3);
13350
+ }
13351
+ });
13352
+
13353
+ // src/cli/train-model.ts
13354
+ var train_model_exports = {};
13355
+ __export(train_model_exports, {
13356
+ trainModel: () => trainModel,
13357
+ trainModelCli: () => trainModelCli
13358
+ });
13359
+ function trainModel(deps = {}) {
13360
+ const gathered = gatherSamples(deps);
13361
+ if (gathered.refusal) {
13362
+ return { outcome: "insufficient-data", warnings: gathered.warnings, detail: gathered.refusal };
13363
+ }
13364
+ const base = loadModel(true);
13365
+ const evalResult = evaluateChain(gathered.samples, base);
13366
+ const classes = new Set(gathered.samples.map((s) => s.label));
13367
+ const finalModel = trainNaiveBayes(gathered.samples);
13368
+ const transferred = transferThresholds(evalResult, gathered.samples, finalModel);
13369
+ let gatePassed = evalResult.gatePassed;
13370
+ if (gatePassed && transferred.acceptThreshold === null) {
13371
+ gatePassed = false;
13372
+ evalResult.reasons.push(
13373
+ "gate FAILED after threshold transfer: the OOF-accepted rows are not confidently classified by the full-data model"
13374
+ );
13375
+ }
13376
+ const file = {
13377
+ formatVersion: 1,
13378
+ model: finalModel,
13379
+ meta: {
13380
+ trainedAt: (/* @__PURE__ */ new Date()).toISOString(),
13381
+ labeledCount: gathered.samples.length,
13382
+ classCount: classes.size,
13383
+ baseVersionAtEval: base.version,
13384
+ gatePassed,
13385
+ acceptThreshold: transferred.acceptThreshold,
13386
+ strongThreshold: transferred.strongThreshold,
13387
+ metrics: { method: evalResult.method, base: evalResult.base, chain: evalResult.chain }
13388
+ }
13389
+ };
13390
+ const outFile = saveLocalModel(file, deps.outFile);
13391
+ evalResult.acceptThreshold = transferred.acceptThreshold ?? evalResult.acceptThreshold;
13392
+ evalResult.strongThreshold = transferred.strongThreshold;
13393
+ return {
13394
+ outcome: gatePassed ? "trained" : "gate-failed",
13395
+ evalResult,
13396
+ baseVersion: base.version,
13397
+ outFile,
13398
+ warnings: gathered.warnings,
13399
+ detail: gatePassed ? `local model ACTIVE - saved to ${outFile}` : `local model saved to ${outFile} but INACTIVE (gate failed) - the reporter stays on the base model`
13400
+ };
13401
+ }
13402
+ function trainModelCli(argv) {
13403
+ const out = (s) => process.stdout.write(`${s}
13404
+ `);
13405
+ const outFile = flagValue(argv, "--out");
13406
+ const projectOnly = hasFlag(argv, "--project");
13407
+ if (outFile !== void 0 && !projectOnly) {
13408
+ out(
13409
+ "[reportforge] note: --out without --project trains on feedback from ALL local projects. If this file will be committed to one repo, consider adding --project so other projects\u2019 token statistics do not ride along."
13410
+ );
13411
+ }
13412
+ const res = trainModel({ outFile, projectOnly });
13413
+ for (const w of res.warnings) out(`[reportforge] warn: ${w}`);
13414
+ if (res.outcome === "insufficient-data") {
13415
+ out(`[reportforge] ${res.detail}`);
13416
+ process.exitCode = 1;
13417
+ return;
13418
+ }
13419
+ out("");
13420
+ out(formatEvalReport(res.evalResult, res.baseVersion));
13421
+ out("");
13422
+ out(`[reportforge] ${res.detail}`);
13423
+ if (res.outcome === "trained" && res.outFile === defaultLocalModelPath()) {
13424
+ out("[reportforge] the reporter picks it up automatically on the next run (failureAnalysis.localModel).");
13425
+ }
13426
+ process.exitCode = res.outcome === "trained" ? 0 : 1;
13427
+ }
13428
+ var init_train_model = __esm({
13429
+ "src/cli/train-model.ts"() {
13430
+ "use strict";
13431
+ init_cjs_shims();
13432
+ init_LocalTrainer();
13433
+ init_LocalEvaluator();
13434
+ init_ModelStore();
13435
+ init_LocalModelStore();
13436
+ init_model_cli_shared();
13437
+ }
13438
+ });
13439
+
13440
+ // src/cli/evaluate-model.ts
13441
+ var evaluate_model_exports = {};
13442
+ __export(evaluate_model_exports, {
13443
+ evaluateModel: () => evaluateModel,
13444
+ evaluateModelCli: () => evaluateModelCli
13445
+ });
13446
+ function evaluateModel(deps = {}) {
13447
+ const lines = [];
13448
+ const base = loadModel(true);
13449
+ if (deps.modelPath !== void 0) {
13450
+ const stored = readLocalModelFile(deps.modelPath);
13451
+ if (!stored) {
13452
+ lines.push(`stored model at ${deps.modelPath}: unreadable or invalid`);
13453
+ } else {
13454
+ const m = stored.meta;
13455
+ lines.push(
13456
+ `stored model at ${deps.modelPath}: trained ${m.trainedAt}, ${m.labeledCount} rows / ${m.classCount} classes, gate ${m.gatePassed ? "PASSED" : "FAILED"}, recorded chain macro-F1 ${m.metrics.chain.macroF1.toFixed(3)} vs base ${m.metrics.base.macroF1.toFixed(3)}`
13457
+ );
13458
+ if (m.baseVersionAtEval !== base.version) {
13459
+ lines.push(
13460
+ `warn: that gate was checked against base v${m.baseVersionAtEval}; base v${base.version} is active now - re-run train-model to re-validate`
13461
+ );
13462
+ }
13463
+ }
13464
+ lines.push("");
13465
+ }
13466
+ const gathered = gatherSamples(deps);
13467
+ for (const w of gathered.warnings) lines.push(`warn: ${w}`);
13468
+ if (gathered.refusal) {
13469
+ lines.push(gathered.refusal);
13470
+ return { ok: false, lines };
13471
+ }
13472
+ lines.push(formatEvalReport(evaluateChain(gathered.samples, base), base.version));
13473
+ lines.push("(nothing was written - run train-model to produce/update the artifact)");
13474
+ return { ok: true, lines };
13475
+ }
13476
+ function evaluateModelCli(argv) {
13477
+ const res = evaluateModel({
13478
+ modelPath: flagValue(argv, "--model"),
13479
+ projectOnly: hasFlag(argv, "--project")
13480
+ });
13481
+ process.stdout.write(res.lines.map((l) => `[reportforge] ${l}`).join("\n") + "\n");
13482
+ process.exitCode = res.ok ? 0 : 1;
13483
+ }
13484
+ var init_evaluate_model = __esm({
13485
+ "src/cli/evaluate-model.ts"() {
13486
+ "use strict";
13487
+ init_cjs_shims();
13488
+ init_LocalEvaluator();
13489
+ init_ModelStore();
13490
+ init_LocalModelStore();
13491
+ init_model_cli_shared();
12690
13492
  }
12691
13493
  });
12692
13494
 
@@ -12704,6 +13506,12 @@ Commands:
12704
13506
  --template=minimal|detailed|executive (default: detailed)
12705
13507
  --output=<path> (default: playwright-report/demo-report.pdf)
12706
13508
  export-feedback Export locally-collected unrecognized failures (tokenized, local-only).
13509
+ label-feedback Interactively label locally-collected failures for on-device training (local-only).
13510
+ train-model Train + evaluate a personal classifier layer from your labeled feedback (local-only).
13511
+ --out=<path> write the model file elsewhere (default: ~/.reportforge/model-local.json)
13512
+ --project train only on feedback from the current directory's project
13513
+ evaluate-model Compare base vs. base+local on your labeled feedback without writing anything.
13514
+ --model=<path> also print a stored model file's metadata and metrics
12707
13515
 
12708
13516
  Run "npx @reportforge/playwright-pdf demo" to see the report ReportForge produces.`;
12709
13517
  function pickCommand(arg) {
@@ -12712,6 +13520,12 @@ function pickCommand(arg) {
12712
13520
  return "demo";
12713
13521
  case "export-feedback":
12714
13522
  return "export-feedback";
13523
+ case "label-feedback":
13524
+ return "label-feedback";
13525
+ case "train-model":
13526
+ return "train-model";
13527
+ case "evaluate-model":
13528
+ return "evaluate-model";
12715
13529
  default:
12716
13530
  return "help";
12717
13531
  }
@@ -12745,6 +13559,21 @@ async function main() {
12745
13559
  exportFeedbackCli2();
12746
13560
  return;
12747
13561
  }
13562
+ case "label-feedback": {
13563
+ const { labelFeedbackCli: labelFeedbackCli2 } = await Promise.resolve().then(() => (init_label_feedback(), label_feedback_exports));
13564
+ await labelFeedbackCli2();
13565
+ return;
13566
+ }
13567
+ case "train-model": {
13568
+ const { trainModelCli: trainModelCli2 } = await Promise.resolve().then(() => (init_train_model(), train_model_exports));
13569
+ trainModelCli2(process.argv.slice(3));
13570
+ return;
13571
+ }
13572
+ case "evaluate-model": {
13573
+ const { evaluateModelCli: evaluateModelCli2 } = await Promise.resolve().then(() => (init_evaluate_model(), evaluate_model_exports));
13574
+ evaluateModelCli2(process.argv.slice(3));
13575
+ return;
13576
+ }
12748
13577
  case "help": {
12749
13578
  const { text, stream, code } = helpOutput(arg);
12750
13579
  (stream === "stdout" ? process.stdout : process.stderr).write(text);