@validate.qa/runner 1.0.100 → 1.0.101

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.
@@ -1815,7 +1815,7 @@ function reportedBugWhere() {
1815
1815
  function bugReviewStatus(status) {
1816
1816
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
1817
1817
  }
1818
- var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
1818
+ var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
1819
1819
  var init_run_classification = __esm({
1820
1820
  "../shared/dist/run-classification.js"() {
1821
1821
  "use strict";
@@ -1835,6 +1835,7 @@ var init_run_classification = __esm({
1835
1835
  UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
1836
1836
  BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
1837
1837
  BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
1838
+ TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
1838
1839
  EMAIL_NOT_RECEIVED_PREFIXES = [
1839
1840
  "VERIFICATION_EMAIL_NOT_RECEIVED:",
1840
1841
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
@@ -2938,176 +2939,6 @@ var init_origin_domain = __esm({
2938
2939
  }
2939
2940
  });
2940
2941
 
2941
- // ../shared/dist/auth-block-detector.js
2942
- function stripEchoLines(text) {
2943
- return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
2944
- }
2945
- function matchesAny(text, patterns) {
2946
- return patterns.some((p) => p.test(text));
2947
- }
2948
- function detectAuthBlock(input) {
2949
- const tags = input.tags ?? [];
2950
- const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
2951
- if (!isAuthed)
2952
- return NO_DETECTION;
2953
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
2954
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
2955
- return NO_DETECTION;
2956
- const sources = [];
2957
- if (input.error)
2958
- sources.push(stripEchoLines(input.error));
2959
- if (input.logs)
2960
- sources.push(stripEchoLines(input.logs));
2961
- for (const sr of input.stepResults ?? []) {
2962
- if (sr.error)
2963
- sources.push(stripEchoLines(sr.error));
2964
- }
2965
- for (const text of sources) {
2966
- if (!text)
2967
- continue;
2968
- for (const sig of AUTH_BLOCK_SIGNATURES) {
2969
- if (sig.re.test(text)) {
2970
- return {
2971
- detected: true,
2972
- reason: sig.reason,
2973
- evidence: [`login-block signature "${sig.reason}" in: "${text.slice(0, 140).replace(/\s+/g, " ").trim()}"`]
2974
- };
2975
- }
2976
- }
2977
- }
2978
- return NO_DETECTION;
2979
- }
2980
- function detectApiColdSessionBlock(input) {
2981
- const tags = input.tags ?? [];
2982
- if (!tags.includes("@api"))
2983
- return NO_DETECTION;
2984
- const cookieMissing = input.authStateExpected && !input.authStateInjected;
2985
- const standingMissing = !!input.needsStandingHeaders && !input.standingHeadersInjected;
2986
- if (!cookieMissing && !standingMissing)
2987
- return NO_DETECTION;
2988
- const has401Status = (input.apiCalls ?? []).some((c) => c && (c.status === 401 || c.status === 403));
2989
- const texts = [];
2990
- if (input.error)
2991
- texts.push(input.error);
2992
- if (input.logs)
2993
- texts.push(input.logs);
2994
- for (const sr of input.stepResults ?? [])
2995
- if (sr.error)
2996
- texts.push(sr.error);
2997
- const has401Text = texts.some((t) => UNAUTHORIZED_TEXT_RE.test(t));
2998
- if (!has401Status && !has401Text)
2999
- return NO_DETECTION;
3000
- const missingAuth = cookieMissing ? "cold injected auth session (401/403 with no storageState)" : "missing standing auth header (401/403 with no API_AUTH_HEADERS installed)";
3001
- const injectedNoun = cookieMissing ? "injected session was absent/expired" : "standing auth header was not installed";
3002
- return {
3003
- detected: true,
3004
- reason: missingAuth,
3005
- evidence: [
3006
- has401Status ? `a captured @api call returned 401/403 while the ${injectedNoun}` : `a 401/403 signal in the run error/logs while the ${injectedNoun}`
3007
- ]
3008
- };
3009
- }
3010
- function coldSessionConfigMessage(context, reason, evidence) {
3011
- return `CONFIG_ISSUE: ${context} without valid injected auth (${reason}) \u2014 no session cookie was injected this run. Ensure the project login (auth-setup recipe / prewarm) is configured so the shared session jar warms (a project with no login: set TEST_USER_EMAIL and TEST_USER_PASSWORD in Test Data \u2192 Test Variables so a session can be captured); for endpoints that authenticate with a standing API key or bearer header instead, set them in the project's API_AUTH_HEADERS variable (injected as request headers). Evidence: ${evidence.join("; ")}`;
3012
- }
3013
- var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, UNAUTHORIZED_TEXT_RE;
3014
- var init_auth_block_detector = __esm({
3015
- "../shared/dist/auth-block-detector.js"() {
3016
- "use strict";
3017
- NO_DETECTION = { detected: false, reason: "", evidence: [] };
3018
- AUTHED_TAGS = ["@needs-auth", "@inline-login"];
3019
- LOGIN_CTX = "\\b(?:log\\s*in|login|logging\\s+in|sign[\\s-]?in|signing\\s+in|continue)\\b";
3020
- AUTH_BLOCK_SIGNATURES = [
3021
- { reason: "incorrect password", re: /\b(incorrect|invalid|wrong|bad)\s+password\b/i },
3022
- { reason: "password is incorrect", re: /\bpassword\s+(is\s+)?(incorrect|invalid|wrong)\b/i },
3023
- { reason: "invalid credentials", re: /\b(invalid|incorrect|wrong|bad)\s+(login\s+)?credentials\b/i },
3024
- { reason: "credentials do not match", re: /\bcredentials\b[^\n]{0,40}\b(do(?:es)?\s*n['’]?t|do\s+not)\s+match\b/i },
3025
- { reason: "incorrect email or password", re: /\b(incorrect|invalid|wrong)\s+(email|username)\s+or\s+password\b/i },
3026
- // Require an EXPLICIT negative (codex #21): the old `(not\s+)?(confirmed|verified)` made the negation
3027
- // optional, so a benign "Email verified" success message / feature assertion matched and hid a real
3028
- // feature failure as a CONFIG_ISSUE. Only "email not (yet) confirmed/verified" or "unconfirmed/unverified".
3029
- { reason: "email not confirmed", re: /\b(e-?mail)\b[^\n]{0,30}\b(not\s+(?:yet\s+)?(?:confirmed|verified)|unconfirmed|unverified)\b/i },
3030
- { reason: "please confirm your email", re: /\bplease\s+(confirm|verify)\s+your\s+(e-?mail)\b/i },
3031
- // The bare "confirm/verify your email" and "account not found" phrases are ALSO ordinary feature copy
3032
- // ("Verify your email banner", a 404-style "Account not found" page), so they only count with a
3033
- // login/sign-in context on the SAME line (either side). "Please confirm/verify your email" above
3034
- // is already an explicit prompt and stays unconditional.
3035
- { reason: "confirm your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bconfirm\\s+your\\s+e-?mail\\b|\\bconfirm\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
3036
- { reason: "verify your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bverify\\s+your\\s+e-?mail\\b|\\bverify\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
3037
- {
3038
- reason: "account not found",
3039
- re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b|\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b[^\\n]{0,80}${LOGIN_CTX}`, "i")
3040
- },
3041
- { reason: "authentication failed", re: /\bauthentication\s+failed\b/i },
3042
- { reason: "login failed", re: /\b(login|log\s*in|sign[\s-]?in)\s+failed\b/i }
3043
- ];
3044
- INTENTIONAL_NEGATIVE_AUTH = [
3045
- /\b(invalid|incorrect|wrong|bad)\s+(password|credentials|email|login)\b/i,
3046
- /\b(negative|unhappy)\s+(login|auth|sign[\s-]?in|path|case|flow|test)\b/i,
3047
- // "should fail" et al must be TIED to an auth noun (codex #21): a bare `should\s+(fail|reject|deny)`
3048
- // exempted benign names like "Export should fail gracefully", silently disabling the anti-lockout
3049
- // protection for them. Require a login/auth noun on either side of the "should fail / fail to log in".
3050
- /\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password)\b[^\n]{0,40}\bshould\s+(fail|reject|deny)\b/i,
3051
- /\bshould\s+(fail|reject|deny|not\s+log(?:\s*in)?)\b[^\n]{0,40}\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password|unauthenticated)\b/i,
3052
- /\bfailed?\s+login\b/i,
3053
- /\blogin\s+(error|failure|rejected?)\b/i,
3054
- /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
3055
- ];
3056
- ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
3057
- UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*:?\s*(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
3058
- }
3059
- });
3060
-
3061
- // ../shared/dist/skip-coverage-detector.js
3062
- function detectSkippedApiCoverage(steps) {
3063
- const allSkipped = steps.length > 0 && steps.every((s) => s.status === "skipped");
3064
- if (allSkipped) {
3065
- if (steps.every((s) => s.skipKind === "fixme")) {
3066
- return {
3067
- detected: false,
3068
- logLine: "All @api tests skipped, but EVERY skip is a deliberate fixme (intentional N/A \u2014 e.g. a UI-only redirect login) \u2014 NOT relabelling CONFIG_ISSUE"
3069
- };
3070
- }
3071
- return {
3072
- detected: true,
3073
- error: (
3074
- // Name BOTH auth mechanisms (item 5d): the all-skipped copy used to say only "refresh the auth
3075
- // state", which misdirects a @auth-standing operator (whose real gap is a missing API_AUTH_HEADERS
3076
- // key, not a cold cookie session) to warm a login that was never the problem.
3077
- "CONFIG_ISSUE: Authenticated @api coverage was NOT exercised \u2014 every test skipped (cold injected session, a missing standing auth header, or an unresolved fixme). Provide the auth the endpoints need \u2014 warm the project login/session cookie (auth-setup recipe / prewarm), or set a standing API key/bearer in the project's API_AUTH_HEADERS variable \u2014 so the assertions actually run."
3078
- ),
3079
- logLine: "All @api tests skipped \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so zero authenticated coverage is visible, not silent green"
3080
- };
3081
- }
3082
- const runtimeSkipped = steps.filter((s) => s.status === "skipped" && s.skipKind === "skip");
3083
- if (runtimeSkipped.length === 0)
3084
- return { detected: false };
3085
- const reasons = [...new Set(runtimeSkipped.map((s) => `"${s.title ?? "test"}"${s.skipReason ? ` (${s.skipReason})` : ""}`))].join("; ");
3086
- return {
3087
- detected: true,
3088
- error: `CONFIG_ISSUE: ${runtimeSkipped.length} of ${steps.length} @api tests skipped at runtime \u2014 that coverage was NOT exercised. Skipped: ${reasons}`,
3089
- logLine: `${runtimeSkipped.length}/${steps.length} @api tests skipped at runtime \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so the coverage hole is visible, not silent green`
3090
- };
3091
- }
3092
- var init_skip_coverage_detector = __esm({
3093
- "../shared/dist/skip-coverage-detector.js"() {
3094
- "use strict";
3095
- }
3096
- });
3097
-
3098
- // ../shared/dist/feature-flags.js
3099
- function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
3100
- return value?.trim().toLowerCase() !== "false";
3101
- }
3102
- function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
3103
- return value?.trim().toLowerCase() !== "false";
3104
- }
3105
- var init_feature_flags = __esm({
3106
- "../shared/dist/feature-flags.js"() {
3107
- "use strict";
3108
- }
3109
- });
3110
-
3111
2942
  // ../shared/dist/repository-cli.js
3112
2943
  function isRepositoryCli(value) {
3113
2944
  return typeof value === "string" && REPOSITORY_CLIS.includes(value);
@@ -4499,7 +4330,7 @@ function unescapeQuotedBody(raw) {
4499
4330
  return raw.replace(/\\(['"`\\])/g, "$1");
4500
4331
  }
4501
4332
  function extractRoleNameValue(line) {
4502
- const m = line.match(new RegExp(`getByRole\\s*\\([^,]+,\\s*\\{[^}]*name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
4333
+ const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
4503
4334
  if (!m)
4504
4335
  return null;
4505
4336
  const raw = m[1] ?? m[2] ?? m[3];
@@ -5207,6 +5038,8 @@ function stripCoverageComments(code) {
5207
5038
  let copyFrom = 0;
5208
5039
  let i = 0;
5209
5040
  let quote = null;
5041
+ let prev = "";
5042
+ let noRegexBefore = 0;
5210
5043
  while (i < code.length) {
5211
5044
  const ch = code[i];
5212
5045
  if (quote) {
@@ -5214,12 +5047,24 @@ function stripCoverageComments(code) {
5214
5047
  i += 2;
5215
5048
  continue;
5216
5049
  }
5217
- if (ch === quote || ch === "\n" && quote !== "`")
5050
+ if (ch === quote || ch === "\n" && quote !== "`") {
5218
5051
  quote = null;
5052
+ prev = ch === "\n" ? prev : ch;
5053
+ }
5219
5054
  i++;
5220
5055
  continue;
5221
5056
  }
5222
5057
  const next = code[i + 1];
5058
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
5059
+ const end = regexLiteralEnd3(code, i);
5060
+ if (end > 0) {
5061
+ i = end;
5062
+ prev = "a";
5063
+ continue;
5064
+ }
5065
+ const eol = code.indexOf("\n", i);
5066
+ noRegexBefore = eol === -1 ? code.length : eol;
5067
+ }
5223
5068
  if (ch === "/" && next === "*") {
5224
5069
  const end = code.indexOf("*/", i + 2);
5225
5070
  const stop = end === -1 ? code.length : end + 2;
@@ -5238,11 +5083,39 @@ function stripCoverageComments(code) {
5238
5083
  }
5239
5084
  if (ch === '"' || ch === "'" || ch === "`")
5240
5085
  quote = ch;
5086
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
5087
+ prev = ch;
5241
5088
  i++;
5242
5089
  }
5243
5090
  out.push(code.slice(copyFrom));
5244
5091
  return out.join("");
5245
5092
  }
5093
+ function regexLiteralEnd3(code, start) {
5094
+ let inClass = false;
5095
+ for (let j = start + 1; j < code.length; j++) {
5096
+ const c = code[j];
5097
+ if (c === "\n")
5098
+ return -1;
5099
+ if (c === "\\") {
5100
+ j++;
5101
+ continue;
5102
+ }
5103
+ if (inClass) {
5104
+ if (c === "]")
5105
+ inClass = false;
5106
+ continue;
5107
+ }
5108
+ if (c === "[")
5109
+ inClass = true;
5110
+ else if (c === "/") {
5111
+ let k = j + 1;
5112
+ while (k < code.length && isIdentChar(code[k]))
5113
+ k++;
5114
+ return k;
5115
+ }
5116
+ }
5117
+ return -1;
5118
+ }
5246
5119
  function isIdentChar(ch) {
5247
5120
  if (!ch)
5248
5121
  return false;
@@ -5900,7 +5773,7 @@ function assessTitleCoverage(title, code) {
5900
5773
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
5901
5774
  return { covered: hit >= needed || repeatedHit, missingTerms };
5902
5775
  }
5903
- var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
5776
+ var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
5904
5777
  var init_expected_outcome_coverage = __esm({
5905
5778
  "../shared/dist/expected-outcome-coverage.js"() {
5906
5779
  "use strict";
@@ -5937,6 +5810,7 @@ var init_expected_outcome_coverage = __esm({
5937
5810
  ]);
5938
5811
  MAX_ASSERTION_JOIN_CHARS = 8e3;
5939
5812
  MAX_COVERAGE_LINE_CHARS = 2e3;
5813
+ REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
5940
5814
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
5941
5815
  cachedAssertionInput = null;
5942
5816
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -6387,7 +6261,7 @@ function detectsErrorBannerAssertion(code) {
6387
6261
  const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
6388
6262
  const lines = cleaned.split("\n");
6389
6263
  const candidates = [];
6390
- const positive = (stmt) => !NEGATED_ASSERT.test(stmt) && !ABSENCE_MATCHER.test(stmt);
6264
+ const positive = (stmt) => !claimsAbsence(stmt);
6391
6265
  const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
6392
6266
  for (const stmt of statements) {
6393
6267
  if (positive(stmt))
@@ -6516,6 +6390,12 @@ function detectsObservedFailureAsExpected(code) {
6516
6390
  return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
6517
6391
  });
6518
6392
  }
6393
+ function claimsAbsence(stmt) {
6394
+ const negated = NEGATED_ASSERT_NOT.test(stmt);
6395
+ const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
6396
+ const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
6397
+ return negated !== absenceMatcher;
6398
+ }
6519
6399
  function isVacuousErrorAbsence(stmt, presence) {
6520
6400
  if (!ABSENCE_MATCHER.test(stmt))
6521
6401
  return false;
@@ -6531,19 +6411,53 @@ function isChromeTarget(stmt) {
6531
6411
  return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
6532
6412
  }
6533
6413
  function countsAsContentText(stmt) {
6534
- if (!TEXT_CONTENT_MATCHER.test(stmt))
6414
+ if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
6535
6415
  return false;
6536
6416
  if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
6537
6417
  return false;
6538
- if (!isChromeTarget(stmt))
6418
+ return onContentTarget(stmt);
6419
+ }
6420
+ function onContentTarget(stmt) {
6421
+ return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
6422
+ }
6423
+ function provesTextRemoved(stmt, presence) {
6424
+ const m = stmt.match(NEGATED_TEXT);
6425
+ if (!m || !onContentTarget(stmt))
6426
+ return false;
6427
+ const text = m[1] ?? m[2];
6428
+ if (text === void 0)
6539
6429
  return true;
6540
- return /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
6430
+ if (!/[\p{L}\p{N}]/u.test(text))
6431
+ return false;
6432
+ return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
6541
6433
  }
6542
6434
  function isDataContentRole(stmt) {
6543
6435
  return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
6544
6436
  }
6437
+ function destructuresRequest(code) {
6438
+ let readTo = -1;
6439
+ for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
6440
+ const start = m.index + m[0].length;
6441
+ if (start <= readTo)
6442
+ continue;
6443
+ let end = start;
6444
+ while (end < code.length && code[end] !== "}" && code[end] !== ")")
6445
+ end++;
6446
+ readTo = end;
6447
+ if (end === code.length)
6448
+ return false;
6449
+ if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
6450
+ continue;
6451
+ let close = end + 1;
6452
+ while (close < code.length && /\s/.test(code[close]))
6453
+ close++;
6454
+ if (code[close] === ")")
6455
+ return true;
6456
+ }
6457
+ return false;
6458
+ }
6545
6459
  function isApiTest(code) {
6546
- const usesRequestFixture = /async\s*\(\s*\{[^})]*\brequest\b[^})]*\}\s*\)/.test(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
6460
+ const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
6547
6461
  if (!usesRequestFixture)
6548
6462
  return false;
6549
6463
  const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
@@ -6802,6 +6716,9 @@ function hasOnlyConstantAssertions(code) {
6802
6716
  const { constant, real } = constantAssertions(code);
6803
6717
  return constant.length > 0 && real === 0;
6804
6718
  }
6719
+ function envValueKey(name) {
6720
+ return `\0env:${name}`;
6721
+ }
6805
6722
  function enteredValues(stmts) {
6806
6723
  const values = /* @__PURE__ */ new Map();
6807
6724
  const navLines = [];
@@ -6810,24 +6727,55 @@ function enteredValues(stmts) {
6810
6727
  navLines.push(startLine);
6811
6728
  let m;
6812
6729
  ENTERED_VALUE.lastIndex = 0;
6813
- while (m = ENTERED_VALUE.exec(stmt)) {
6814
- const value = (m[1] ?? m[2] ?? m[3]).toLowerCase();
6815
- if (!values.has(value))
6816
- values.set(value, startLine);
6817
- }
6730
+ const add = (value) => {
6731
+ const lines = values.get(value);
6732
+ if (!lines)
6733
+ values.set(value, [startLine]);
6734
+ else if (lines[lines.length - 1] !== startLine)
6735
+ lines.push(startLine);
6736
+ };
6737
+ while (m = ENTERED_VALUE.exec(stmt))
6738
+ add((m[1] ?? m[2] ?? m[3]).toLowerCase());
6739
+ ENTERED_ENV.lastIndex = 0;
6740
+ while (m = ENTERED_ENV.exec(stmt))
6741
+ add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
6818
6742
  }
6819
6743
  return { values, navLines };
6820
6744
  }
6821
- function isEnteredValueEcho(stmt, entered, startLine) {
6745
+ function countBelow(lines, line) {
6746
+ let lo = 0;
6747
+ let hi = lines.length;
6748
+ while (lo < hi) {
6749
+ const mid = lo + hi >> 1;
6750
+ if (lines[mid] < line)
6751
+ lo = mid + 1;
6752
+ else
6753
+ hi = mid;
6754
+ }
6755
+ return lo;
6756
+ }
6757
+ function enteredLinesOf(stmt, entered) {
6822
6758
  const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
6823
- if (!m)
6759
+ if (m)
6760
+ return entered.values.get(m[2].toLowerCase());
6761
+ const env = stmt.match(HAVE_VALUE_ENV);
6762
+ return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
6763
+ }
6764
+ function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
6765
+ if (!lines || startLine === void 0)
6766
+ return false;
6767
+ const before = countBelow(lines, startLine);
6768
+ if (before === 0)
6824
6769
  return false;
6825
- const enteredAt = entered.values.get(m[2].toLowerCase());
6826
- if (enteredAt === void 0)
6770
+ return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
6771
+ }
6772
+ function isEnteredValueEcho(stmt, entered, startLine) {
6773
+ const lines = enteredLinesOf(stmt, entered);
6774
+ if (!lines)
6827
6775
  return false;
6828
- if (startLine === void 0)
6776
+ if (startLine === void 0 || lines[0] >= startLine)
6829
6777
  return true;
6830
- return !entered.navLines.some((n) => n > enteredAt && n < startLine);
6778
+ return typedSinceLastNav(stmt, entered, startLine, lines);
6831
6779
  }
6832
6780
  function isAnyValueMatcher(stmt) {
6833
6781
  if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
@@ -6917,6 +6865,43 @@ function hasAttributeTransition(stmts) {
6917
6865
  return true;
6918
6866
  return false;
6919
6867
  }
6868
+ function comparesWithItself(subject, tail) {
6869
+ const m = SELF_COMPARE_MATCHER.exec(tail);
6870
+ if (!m)
6871
+ return false;
6872
+ const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
6873
+ if (args === null)
6874
+ return false;
6875
+ const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
6876
+ const expected = squash2(args);
6877
+ return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
6878
+ }
6879
+ function asPlainAssertion(s) {
6880
+ const { stmt } = s;
6881
+ if (COUNT_AT_LEAST_ZERO.test(stmt))
6882
+ return [];
6883
+ const subject = extractExpectSubject(stmt);
6884
+ if (subject === null)
6885
+ return [s];
6886
+ const plain = subject.replace(/^await\s+/, "").trim();
6887
+ const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
6888
+ const tail = stmt.slice(subjectAt + subject.length).trimStart();
6889
+ const as = (rewritten) => [{ ...s, stmt: rewritten }];
6890
+ if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
6891
+ return [];
6892
+ if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
6893
+ return [];
6894
+ if (comparesWithItself(plain, tail))
6895
+ return [];
6896
+ const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
6897
+ if (visible && IS_VISIBLE_TRUE.test(tail))
6898
+ return as(`await expect(${visible[1]}).toBeVisible();`);
6899
+ if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
6900
+ const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
6901
+ return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
6902
+ }
6903
+ return [s];
6904
+ }
6920
6905
  function creditsOutcomeAlways(stmt, entered, startLine) {
6921
6906
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
6922
6907
  return false;
@@ -6990,6 +6975,8 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
6990
6975
  continue;
6991
6976
  if (isVacuousErrorAbsence(stmt, presence))
6992
6977
  continue;
6978
+ if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
6979
+ continue;
6993
6980
  if (creditsOutcomeAlways(stmt, entered, startLine))
6994
6981
  return { kind: "durable" };
6995
6982
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -6999,12 +6986,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
6999
6986
  else
7000
6987
  return { kind: "durable" };
7001
6988
  }
7002
- if (STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
6989
+ const retyped = typedSinceLastNav(stmt, entered, startLine);
6990
+ if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
7003
6991
  return { kind: "durable" };
7004
- if (provesStoredControlValue(stmt, startLine, persistence))
6992
+ if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
7005
6993
  return { kind: "durable" };
7006
6994
  if (countsAsContentText(stmt))
7007
6995
  return { kind: "durable" };
6996
+ if (provesTextRemoved(stmt, presence))
6997
+ return { kind: "durable" };
7008
6998
  if (mutated && VISIBILITY_MATCHER.test(stmt)) {
7009
6999
  const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
7010
7000
  if (revealsText && !isChromeTarget(stmt)) {
@@ -7071,7 +7061,7 @@ function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMut
7071
7061
  function validateAssertionStrength(code, opts = {}) {
7072
7062
  const body = stripLoginScaffolding(code);
7073
7063
  const lines = body.split("\n");
7074
- const stmts = liveExpectStatements(stripCoverageComments(body));
7064
+ const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
7075
7065
  const base = (extra) => ({
7076
7066
  apiTest: false,
7077
7067
  interacted: false,
@@ -7277,7 +7267,7 @@ function validateAssertionStrength(code, opts = {}) {
7277
7267
  signals
7278
7268
  };
7279
7269
  }
7280
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, IN_FLIGHT_TEXT, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
7270
+ var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
7281
7271
  var init_assertion_strength = __esm({
7282
7272
  "../shared/dist/assertion-strength.js"() {
7283
7273
  "use strict";
@@ -7302,8 +7292,8 @@ var init_assertion_strength = __esm({
7302
7292
  // form field value
7303
7293
  /\.toMatch\s*\(/,
7304
7294
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
7305
- /\.toBeDisabled\s*\(/,
7306
- // a control became DISABLED — a gating/validation outcome
7295
+ /(?<!\.not\s*)\.\s*toBeDisabled\s*\(/,
7296
+ // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
7307
7297
  /\.not\.toBeVisible\s*\(/,
7308
7298
  /\.toBeHidden\s*\(/
7309
7299
  // something the action removed/closed (disappearance proof)
@@ -7311,7 +7301,7 @@ var init_assertion_strength = __esm({
7311
7301
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
7312
7302
  ANY_CLICK = /\.click\s*\(/;
7313
7303
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
7314
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
7304
+ URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)|\bexpect\s*\.\s*poll\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?page\s*\.\s*url\s*\(\s*\)/;
7315
7305
  RELOAD = /\.reload\s*\(/;
7316
7306
  EXPECT_STMT = /\bexpect\s*[.(]/;
7317
7307
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
@@ -7340,6 +7330,7 @@ var init_assertion_strength = __esm({
7340
7330
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
7341
7331
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
7342
7332
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
7333
+ NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
7343
7334
  CONST_CHAIN_MAX_DEPTH = 5;
7344
7335
  TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
7345
7336
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
@@ -7355,8 +7346,14 @@ var init_assertion_strength = __esm({
7355
7346
  CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
7356
7347
  GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
7357
7348
  ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
7349
+ VALUE_ABSENCE = /\.(?:count\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)|toBeLessThan\s*\(\s*1\s*\))|isVisible\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*false\s*\)|toBeFalsy\s*\()|isHidden\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*true\s*\)|toBeTruthy\s*\())/;
7350
+ OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
7351
+ NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
7358
7352
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
7359
7353
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
7354
+ NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
7355
+ ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
7356
+ REQUEST_WORD = /\brequest\b/;
7360
7357
  ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
7361
7358
  INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
7362
7359
  ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
@@ -7377,7 +7374,20 @@ var init_assertion_strength = __esm({
7377
7374
  MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
7378
7375
  ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
7379
7376
  PAGE_NAV = /\.(?:goto|reload)\s*\(/;
7377
+ ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
7378
+ ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
7379
+ HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
7380
7380
  IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
7381
+ LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
7382
+ LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
7383
+ ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
7384
+ COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
7385
+ DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
7386
+ ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
7387
+ SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
7388
+ IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
7389
+ PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
7390
+ COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
7381
7391
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
7382
7392
  CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
7383
7393
  COPY_CONTROL = /\bcopy\b/i;
@@ -8859,51 +8869,188 @@ function deriveAuthPlan(tags, code, opts) {
8859
8869
  const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
8860
8870
  const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
8861
8871
  return {
8862
- version: 1,
8863
- source: "legacy",
8864
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
8865
- presentation: authedLegacy ? "cookie" : "none",
8866
- needsCookieState: cookie,
8867
- needsStandingHeaders: false,
8868
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
8869
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
8870
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
8871
- needsLoginCreds,
8872
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
8872
+ version: 1,
8873
+ source: "legacy",
8874
+ acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
8875
+ presentation: authedLegacy ? "cookie" : "none",
8876
+ needsCookieState: cookie,
8877
+ needsStandingHeaders: false,
8878
+ // A legacy bare-@api test keeps injecting standing headers IF the project configured them
8879
+ // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
8880
+ standingHeadersMode: isApi ? "legacy-if-configured" : "none",
8881
+ needsLoginCreds,
8882
+ requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
8883
+ };
8884
+ }
8885
+ function authTypeTag(tags) {
8886
+ const t = Array.isArray(tags) ? tags : [];
8887
+ const explicit = explicitAuthTypeTags(t);
8888
+ return explicit.length === 1 ? explicit[0] : void 0;
8889
+ }
8890
+ var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
8891
+ var init_auth_plan = __esm({
8892
+ "../shared/dist/auth-plan.js"() {
8893
+ "use strict";
8894
+ AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
8895
+ AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
8896
+ AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
8897
+ AUTH_TAG_STANDING = "@auth-standing";
8898
+ AUTH_TAG_NONE = "@auth-none";
8899
+ AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
8900
+ AUTH_TYPE_TAGS = [
8901
+ AUTH_TAG_COOKIE_STATE,
8902
+ AUTH_TAG_LOGIN_COOKIE,
8903
+ AUTH_TAG_LOGIN_BEARER,
8904
+ AUTH_TAG_STANDING,
8905
+ AUTH_TAG_NONE,
8906
+ AUTH_TAG_UNSUPPORTED
8907
+ ];
8908
+ AUTH_TAG_ALIASES = {
8909
+ "@auth-cookie": AUTH_TAG_COOKIE_STATE,
8910
+ "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
8911
+ "@auth-token": AUTH_TAG_LOGIN_BEARER,
8912
+ "@auth-api-key": AUTH_TAG_STANDING,
8913
+ "@auth-standing-header": AUTH_TAG_STANDING
8914
+ };
8915
+ TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
8916
+ API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
8917
+ }
8918
+ });
8919
+
8920
+ // ../shared/dist/auth-block-detector.js
8921
+ function stripEchoLines(text) {
8922
+ return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
8923
+ }
8924
+ function matchesAny(text, patterns) {
8925
+ return patterns.some((p) => p.test(text));
8926
+ }
8927
+ function detectAuthBlock(input) {
8928
+ const tags = input.tags ?? [];
8929
+ const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
8930
+ if (!isAuthed)
8931
+ return NO_DETECTION;
8932
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
8933
+ if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
8934
+ return NO_DETECTION;
8935
+ const sources = [];
8936
+ if (input.error)
8937
+ sources.push(stripEchoLines(input.error));
8938
+ if (input.logs)
8939
+ sources.push(stripEchoLines(input.logs));
8940
+ for (const sr of input.stepResults ?? []) {
8941
+ if (sr.error)
8942
+ sources.push(stripEchoLines(sr.error));
8943
+ }
8944
+ for (const text of sources) {
8945
+ if (!text)
8946
+ continue;
8947
+ for (const sig of AUTH_BLOCK_SIGNATURES) {
8948
+ if (sig.re.test(text)) {
8949
+ return {
8950
+ detected: true,
8951
+ reason: sig.reason,
8952
+ evidence: [`login-block signature "${sig.reason}" in: "${text.slice(0, 140).replace(/\s+/g, " ").trim()}"`]
8953
+ };
8954
+ }
8955
+ }
8956
+ }
8957
+ return NO_DETECTION;
8958
+ }
8959
+ function detectApiColdSessionBlock(input) {
8960
+ const tags = input.tags ?? [];
8961
+ if (!tags.includes("@api"))
8962
+ return NO_DETECTION;
8963
+ const cookieMissing = input.authStateExpected && !input.authStateInjected;
8964
+ const standingMissing = !!input.needsStandingHeaders && !input.standingHeadersInjected;
8965
+ if (!cookieMissing && !standingMissing)
8966
+ return NO_DETECTION;
8967
+ const has401Status = (input.apiCalls ?? []).some((c) => c && (c.status === 401 || c.status === 403));
8968
+ const texts = [];
8969
+ if (input.error)
8970
+ texts.push(input.error);
8971
+ if (input.logs)
8972
+ texts.push(input.logs);
8973
+ for (const sr of input.stepResults ?? [])
8974
+ if (sr.error)
8975
+ texts.push(sr.error);
8976
+ const has401Text = texts.some((t) => UNAUTHORIZED_TEXT_RE.test(t));
8977
+ if (!has401Status && !has401Text)
8978
+ return NO_DETECTION;
8979
+ const missingAuth = cookieMissing ? "cold injected auth session (401/403 with no storageState)" : "missing standing auth header (401/403 with no API_AUTH_HEADERS installed)";
8980
+ const injectedNoun = cookieMissing ? "injected session was absent/expired" : "standing auth header was not installed";
8981
+ return {
8982
+ detected: true,
8983
+ reason: missingAuth,
8984
+ evidence: [
8985
+ has401Status ? `a captured @api call returned 401/403 while the ${injectedNoun}` : `a 401/403 signal in the run error/logs while the ${injectedNoun}`
8986
+ ]
8873
8987
  };
8874
8988
  }
8875
- function authTypeTag(tags) {
8876
- const t = Array.isArray(tags) ? tags : [];
8877
- const explicit = explicitAuthTypeTags(t);
8878
- return explicit.length === 1 ? explicit[0] : void 0;
8989
+ function coldSessionConfigMessage(context, reason, evidence) {
8990
+ return `CONFIG_ISSUE: ${context} without valid injected auth (${reason}) \u2014 no session cookie was injected this run. Ensure the project login (auth-setup recipe / prewarm) is configured so the shared session jar warms (a project with no login: set TEST_USER_EMAIL and TEST_USER_PASSWORD in Test Data \u2192 Test Variables so a session can be captured); for endpoints that authenticate with a standing API key or bearer header instead, set them in the project's API_AUTH_HEADERS variable (injected as request headers). Evidence: ${evidence.join("; ")}`;
8879
8991
  }
8880
- var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
8881
- var init_auth_plan = __esm({
8882
- "../shared/dist/auth-plan.js"() {
8992
+ var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, UNAUTHORIZED_TEXT_RE;
8993
+ var init_auth_block_detector = __esm({
8994
+ "../shared/dist/auth-block-detector.js"() {
8883
8995
  "use strict";
8884
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
8885
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
8886
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
8887
- AUTH_TAG_STANDING = "@auth-standing";
8888
- AUTH_TAG_NONE = "@auth-none";
8889
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
8890
- AUTH_TYPE_TAGS = [
8891
- AUTH_TAG_COOKIE_STATE,
8892
- AUTH_TAG_LOGIN_COOKIE,
8893
- AUTH_TAG_LOGIN_BEARER,
8894
- AUTH_TAG_STANDING,
8895
- AUTH_TAG_NONE,
8896
- AUTH_TAG_UNSUPPORTED
8996
+ NO_DETECTION = { detected: false, reason: "", evidence: [] };
8997
+ AUTHED_TAGS = ["@needs-auth", "@inline-login"];
8998
+ LOGIN_CTX = "\\b(?:log\\s*in|login|logging\\s+in|sign[\\s-]?in|signing\\s+in|continue)\\b";
8999
+ AUTH_BLOCK_SIGNATURES = [
9000
+ { reason: "incorrect password", re: /\b(incorrect|invalid|wrong|bad)\s+password\b/i },
9001
+ { reason: "password is incorrect", re: /\bpassword\s+(is\s+)?(incorrect|invalid|wrong)\b/i },
9002
+ { reason: "invalid credentials", re: /\b(invalid|incorrect|wrong|bad)\s+(login\s+)?credentials\b/i },
9003
+ { reason: "credentials do not match", re: /\bcredentials\b[^\n]{0,40}\b(do(?:es)?\s*n['’]?t|do\s+not)\s+match\b/i },
9004
+ { reason: "incorrect email or password", re: /\b(incorrect|invalid|wrong)\s+(email|username)\s+or\s+password\b/i },
9005
+ // Require an EXPLICIT negative (codex #21): the old `(not\s+)?(confirmed|verified)` made the negation
9006
+ // optional, so a benign "Email verified" success message / feature assertion matched and hid a real
9007
+ // feature failure as a CONFIG_ISSUE. Only "email not (yet) confirmed/verified" or "unconfirmed/unverified".
9008
+ { reason: "email not confirmed", re: /\b(e-?mail)\b[^\n]{0,30}\b(not\s+(?:yet\s+)?(?:confirmed|verified)|unconfirmed|unverified)\b/i },
9009
+ { reason: "please confirm your email", re: /\bplease\s+(confirm|verify)\s+your\s+(e-?mail)\b/i },
9010
+ // The bare "confirm/verify your email" and "account not found" phrases are ALSO ordinary feature copy
9011
+ // ("Verify your email banner", a 404-style "Account not found" page), so they only count with a
9012
+ // login/sign-in context on the SAME line (either side). "Please confirm/verify your email" above
9013
+ // is already an explicit prompt and stays unconditional.
9014
+ { reason: "confirm your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bconfirm\\s+your\\s+e-?mail\\b|\\bconfirm\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
9015
+ { reason: "verify your email to continue", re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\bverify\\s+your\\s+e-?mail\\b|\\bverify\\s+your\\s+e-?mail\\b[^\\n]{0,80}${LOGIN_CTX}`, "i") },
9016
+ {
9017
+ reason: "account not found",
9018
+ re: new RegExp(`${LOGIN_CTX}[^\\n]{0,80}\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b|\\baccount\\b[^\\n]{0,20}\\b(?:not\\s+found|does(?:n['\u2019]t| not)\\s+exist)\\b[^\\n]{0,80}${LOGIN_CTX}`, "i")
9019
+ },
9020
+ { reason: "authentication failed", re: /\bauthentication\s+failed\b/i },
9021
+ { reason: "login failed", re: /\b(login|log\s*in|sign[\s-]?in)\s+failed\b/i }
8897
9022
  ];
8898
- AUTH_TAG_ALIASES = {
8899
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
8900
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
8901
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
8902
- "@auth-api-key": AUTH_TAG_STANDING,
8903
- "@auth-standing-header": AUTH_TAG_STANDING
8904
- };
8905
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
8906
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
9023
+ INTENTIONAL_NEGATIVE_AUTH = [
9024
+ /\b(invalid|incorrect|wrong|bad)\s+(password|credentials|email|login)\b/i,
9025
+ /\b(negative|unhappy)\s+(login|auth|sign[\s-]?in|path|case|flow|test)\b/i,
9026
+ // "should fail" et al must be TIED to an auth noun (codex #21): a bare `should\s+(fail|reject|deny)`
9027
+ // exempted benign names like "Export should fail gracefully", silently disabling the anti-lockout
9028
+ // protection for them. Require a login/auth noun on either side of the "should fail / fail to log in".
9029
+ /\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password)\b[^\n]{0,40}\bshould\s+(fail|reject|deny)\b/i,
9030
+ /\bshould\s+(fail|reject|deny|not\s+log(?:\s*in)?)\b[^\n]{0,40}\b(login|log\s*in|sign[\s-]?in|auth\w*|credential|password|unauthenticated)\b/i,
9031
+ /\bfailed?\s+login\b/i,
9032
+ /\blogin\s+(error|failure|rejected?)\b/i,
9033
+ /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9034
+ ];
9035
+ ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9036
+ UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*:?\s*(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
9037
+ }
9038
+ });
9039
+
9040
+ // ../shared/dist/auth-session-stamp.js
9041
+ function parseAuthSessionStamp(value) {
9042
+ if (!value || typeof value !== "object" || Array.isArray(value))
9043
+ return null;
9044
+ const { shared, role, generation } = value;
9045
+ if (shared !== true || role !== "producer" && role !== "follower")
9046
+ return null;
9047
+ return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
9048
+ }
9049
+ var AUTH_SESSION_GENERATION_RE;
9050
+ var init_auth_session_stamp = __esm({
9051
+ "../shared/dist/auth-session-stamp.js"() {
9052
+ "use strict";
9053
+ AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
8907
9054
  }
8908
9055
  });
8909
9056
 
@@ -9092,6 +9239,43 @@ var init_auth_state_freshness = __esm({
9092
9239
  }
9093
9240
  });
9094
9241
 
9242
+ // ../shared/dist/skip-coverage-detector.js
9243
+ function detectSkippedApiCoverage(steps) {
9244
+ const allSkipped = steps.length > 0 && steps.every((s) => s.status === "skipped");
9245
+ if (allSkipped) {
9246
+ if (steps.every((s) => s.skipKind === "fixme")) {
9247
+ return {
9248
+ detected: false,
9249
+ logLine: "All @api tests skipped, but EVERY skip is a deliberate fixme (intentional N/A \u2014 e.g. a UI-only redirect login) \u2014 NOT relabelling CONFIG_ISSUE"
9250
+ };
9251
+ }
9252
+ return {
9253
+ detected: true,
9254
+ error: (
9255
+ // Name BOTH auth mechanisms (item 5d): the all-skipped copy used to say only "refresh the auth
9256
+ // state", which misdirects a @auth-standing operator (whose real gap is a missing API_AUTH_HEADERS
9257
+ // key, not a cold cookie session) to warm a login that was never the problem.
9258
+ "CONFIG_ISSUE: Authenticated @api coverage was NOT exercised \u2014 every test skipped (cold injected session, a missing standing auth header, or an unresolved fixme). Provide the auth the endpoints need \u2014 warm the project login/session cookie (auth-setup recipe / prewarm), or set a standing API key/bearer in the project's API_AUTH_HEADERS variable \u2014 so the assertions actually run."
9259
+ ),
9260
+ logLine: "All @api tests skipped \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so zero authenticated coverage is visible, not silent green"
9261
+ };
9262
+ }
9263
+ const runtimeSkipped = steps.filter((s) => s.status === "skipped" && s.skipKind === "skip");
9264
+ if (runtimeSkipped.length === 0)
9265
+ return { detected: false };
9266
+ const reasons = [...new Set(runtimeSkipped.map((s) => `"${s.title ?? "test"}"${s.skipReason ? ` (${s.skipReason})` : ""}`))].join("; ");
9267
+ return {
9268
+ detected: true,
9269
+ error: `CONFIG_ISSUE: ${runtimeSkipped.length} of ${steps.length} @api tests skipped at runtime \u2014 that coverage was NOT exercised. Skipped: ${reasons}`,
9270
+ logLine: `${runtimeSkipped.length}/${steps.length} @api tests skipped at runtime \u2014 posting ERROR/CONFIG_ISSUE (not PASSED) so the coverage hole is visible, not silent green`
9271
+ };
9272
+ }
9273
+ var init_skip_coverage_detector = __esm({
9274
+ "../shared/dist/skip-coverage-detector.js"() {
9275
+ "use strict";
9276
+ }
9277
+ });
9278
+
9095
9279
  // ../shared/dist/feature-coverage.js
9096
9280
  function computeFeatureCoverage(input) {
9097
9281
  const { tests } = input;
@@ -9783,6 +9967,19 @@ var init_deepgram_speech = __esm({
9783
9967
  }
9784
9968
  });
9785
9969
 
9970
+ // ../shared/dist/feature-flags.js
9971
+ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
9972
+ return value?.trim().toLowerCase() !== "false";
9973
+ }
9974
+ function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
9975
+ return value?.trim().toLowerCase() !== "false";
9976
+ }
9977
+ var init_feature_flags = __esm({
9978
+ "../shared/dist/feature-flags.js"() {
9979
+ "use strict";
9980
+ }
9981
+ });
9982
+
9786
9983
  // ../shared/dist/analytics-url.js
9787
9984
  function isSensitiveQueryKey(key) {
9788
9985
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -10085,7 +10282,7 @@ var init_agentic_cli_runtime = __esm({
10085
10282
  GROK_CLI_DEFAULT_MODEL = "grok-4.7";
10086
10283
  GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
10087
10284
  CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
10088
- CLAUDE_CLI_DEFAULT_EFFORT = "max";
10285
+ CLAUDE_CLI_DEFAULT_EFFORT = "high";
10089
10286
  MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
10090
10287
  MUSE_CLI_DEFAULT_EFFORT = "high";
10091
10288
  CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
@@ -10136,9 +10333,25 @@ function isOpenCodeGoModelId(id) {
10136
10333
  function hasOpenCodeGoPrefix(id) {
10137
10334
  return (id?.trim() ?? "").startsWith(OPENCODE_GO_PREFIX);
10138
10335
  }
10336
+ function hasOpenCodeGo2Prefix(id) {
10337
+ return (id?.trim() ?? "").startsWith(OPENCODE_GO_2_PREFIX);
10338
+ }
10139
10339
  function openCodeGoWireModel(id) {
10140
10340
  const trimmed = id?.trim() ?? "";
10141
- return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10341
+ if (!isOpenCodeGoModelId(trimmed))
10342
+ return null;
10343
+ return trimmed.slice((hasOpenCodeGo2Prefix(trimmed) ? OPENCODE_GO_2_PREFIX : OPENCODE_GO_PREFIX).length);
10344
+ }
10345
+ function isZaiModelId(id) {
10346
+ const trimmed = id?.trim() ?? "";
10347
+ return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
10348
+ }
10349
+ function hasZaiPrefix(id) {
10350
+ return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
10351
+ }
10352
+ function zaiWireModel(id) {
10353
+ const trimmed = id?.trim() ?? "";
10354
+ return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
10142
10355
  }
10143
10356
  function isEngineId(value) {
10144
10357
  return CLI_ENGINE_MODEL_IDS.includes(value);
@@ -10236,7 +10449,7 @@ function museCliContributorMode(env = process.env) {
10236
10449
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10237
10450
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10238
10451
  }
10239
- var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN;
10452
+ var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_2_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
10240
10453
  var init_engine_spec = __esm({
10241
10454
  "../shared/dist/engine-spec.js"() {
10242
10455
  "use strict";
@@ -10295,7 +10508,10 @@ var init_engine_spec = __esm({
10295
10508
  OPENROUTER_PREFIX = "openrouter:";
10296
10509
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10297
10510
  OPENCODE_GO_PREFIX = "opencode-go:";
10298
- OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
10511
+ OPENCODE_GO_2_PREFIX = "opencode-go-2:";
10512
+ OPENCODE_GO_MODEL_PATTERN = /^opencode-go(?:-2)?:[a-z0-9][a-z0-9._-]{0,100}$/;
10513
+ ZAI_PREFIX = "zai:";
10514
+ ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10299
10515
  }
10300
10516
  });
10301
10517
 
@@ -10580,6 +10796,8 @@ var init_api_model_catalog = __esm({
10580
10796
  authCaptureModel: "Login capture",
10581
10797
  plannerModel: "Scenario Planner",
10582
10798
  plannerGateModel: "Planner Gate",
10799
+ pwAgentsHealerModel: "Test Healer (Playwright agents)",
10800
+ pwAgentsReviewModel: "Plan Review (Playwright agents)",
10583
10801
  generationModel: "Code Generation",
10584
10802
  reviewModel: "Code Review",
10585
10803
  apiTestModel: "API Test Generator",
@@ -10604,11 +10822,6 @@ var init_api_model_catalog = __esm({
10604
10822
  });
10605
10823
 
10606
10824
  // ../shared/dist/provider-chains.js
10607
- function isClaudeModel(id) {
10608
- if (/claude|anthropic/i.test(id))
10609
- return true;
10610
- return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
10611
- }
10612
10825
  function chainKey(modelId) {
10613
10826
  const id = canonicalModelId(modelId);
10614
10827
  if (!id)
@@ -10616,34 +10829,83 @@ function chainKey(modelId) {
10616
10829
  const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
10617
10830
  return entry?.id ?? id;
10618
10831
  }
10832
+ function has(map, key) {
10833
+ return Object.prototype.hasOwnProperty.call(map, key);
10834
+ }
10619
10835
  function providerChainFor(modelId) {
10620
10836
  const key = chainKey(modelId);
10621
- if (!key || isClaudeModel(key))
10622
- return [];
10623
- return Object.prototype.hasOwnProperty.call(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
10837
+ return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
10838
+ }
10839
+ function providerChainLeadsFor(modelId) {
10840
+ const key = chainKey(modelId);
10841
+ return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
10842
+ }
10843
+ function allChains() {
10844
+ return [...Object.entries(PROVIDER_CHAINS), ...Object.entries(PROVIDER_CHAIN_LEADS)];
10624
10845
  }
10625
10846
  function isProviderChainHopId(modelId) {
10626
10847
  const id = modelId ?? "";
10627
- return id !== "" && Object.values(PROVIDER_CHAINS).some((hops) => hops.includes(id));
10848
+ return id !== "" && allChains().some(([, hops]) => hops.includes(id));
10628
10849
  }
10629
10850
  function providerChainPrimariesForHop(hopId) {
10630
10851
  const id = hopId ?? "";
10631
10852
  if (!id)
10632
10853
  return [];
10633
- return Object.entries(PROVIDER_CHAINS).filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
10854
+ return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
10855
+ }
10856
+ function providerChainHostOf(value, modelValue) {
10857
+ if (value === modelValue)
10858
+ return "own";
10859
+ if (hasZaiPrefix(value))
10860
+ return "zai";
10861
+ if (hasOpenCodeGo2Prefix(value))
10862
+ return "opencode-go-2";
10863
+ if (hasOpenCodeGoPrefix(value))
10864
+ return "opencode-go";
10865
+ if (hasOpenRouterPrefix(value))
10866
+ return "openrouter";
10867
+ return "own";
10868
+ }
10869
+ function providerHostPlan(modelId) {
10870
+ const key = chainKey(modelId);
10871
+ if (!key)
10872
+ return [];
10873
+ const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
10874
+ const steps = [
10875
+ ...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
10876
+ own,
10877
+ ...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
10878
+ ];
10879
+ const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
10880
+ for (const [host, reason] of Object.entries(notOffered)) {
10881
+ const at = steps.findIndex((step) => step === own) + 1;
10882
+ steps.splice(at, 0, { host, value: null, notOffered: reason });
10883
+ }
10884
+ return steps;
10634
10885
  }
10635
- var PROVIDER_CHAINS;
10886
+ var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED;
10636
10887
  var init_provider_chains = __esm({
10637
10888
  "../shared/dist/provider-chains.js"() {
10638
10889
  "use strict";
10639
10890
  init_api_model_catalog();
10891
+ init_engine_spec();
10640
10892
  PROVIDER_CHAINS = {
10641
- "minimax-m3": ["openrouter:minimax/minimax-m3", "opencode-go:minimax-m3"],
10642
- "minimax-m3.1-flash": ["openrouter:minimax/minimax-m3.1-flash", "opencode-go:minimax-m3.1-flash"],
10893
+ "minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
10894
+ "minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
10643
10895
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
10644
- "openrouter:z-ai/glm-5.3-flash": ["opencode-go:glm-5.3-flash"],
10645
- "openrouter:z-ai/glm-5.3": ["opencode-go:glm-5.3"],
10646
- "openrouter:z-ai/glm-5.2": ["opencode-go:glm-5.2"]
10896
+ "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
10897
+ "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
10898
+ "deepseek-flash": ["opencode-go-2:deepseek-v4.1-flash", "opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
10899
+ "deepseek-v4-pro": ["opencode-go-2:deepseek-v4-pro", "opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
10900
+ };
10901
+ PROVIDER_CHAIN_LEADS = {
10902
+ "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go-2:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
10903
+ "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go-2:glm-5.3", "opencode-go:glm-5.3"],
10904
+ "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go-2:glm-5.2", "opencode-go:glm-5.2"]
10905
+ };
10906
+ PROVIDER_HOST_NOT_OFFERED = {
10907
+ "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
10908
+ "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
10647
10909
  };
10648
10910
  }
10649
10911
  });
@@ -12581,7 +12843,7 @@ function stateLiteralsOf(line) {
12581
12843
  const out = [];
12582
12844
  for (const m of line.matchAll(STRING_LITERAL_RE)) {
12583
12845
  const text = m[1] ?? m[2] ?? m[3] ?? "";
12584
- if (text.includes("placeholder="))
12846
+ if (PLACEHOLDER_ATTR_RE.test(text))
12585
12847
  continue;
12586
12848
  if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
12587
12849
  continue;
@@ -12677,7 +12939,7 @@ function formatReadinessPollAdvisory(findings) {
12677
12939
  const where = findings.length === 1 ? "One place" : `${findings.length} places`;
12678
12940
  return `${where} re-check a background state once and assert the finished state at once (${short(first.line, 90)} then ${short(first.assertion, 90)}). ${first.reason}`;
12679
12941
  }
12680
- var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
12942
+ var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_ATTR_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
12681
12943
  var init_readiness_poll = __esm({
12682
12944
  "../shared/dist/readiness-poll.js"() {
12683
12945
  "use strict";
@@ -12690,7 +12952,7 @@ var init_readiness_poll = __esm({
12690
12952
  STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
12691
12953
  RECHECK_NAME_RE = /^\s{0,10}(?:check again|check (?:status|availability|now|for updates|progress)|re-?check(?: status)?|update status|see (?:latest|updates))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12692
12954
  RETRY_NAME_RE = /^\s{0,10}(?:refresh(?: (?:status|list|page|results|now))?|reload(?: (?:page|results|list))?|try again|retry|load (?:again|latest))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
12693
- PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b[a-z]{3,40}(?<!th)ing(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
12955
+ PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b(?:saving|uploading|downloading|signing|logging|submitting|creating|updating|deleting|importing|exporting|publishing|connecting|redirecting|authenticating|installing|retrieving|fetching|analy[sz]ing|running|rendering|restarting|building|compiling|booting|provisioning|deploying|indexing|migrating|launching|initiali[sz]ing|optimi[sz]ing|converting|processing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}(?:starting|scanning|working|applying|finishing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}[a-z]{3,40}(?<!th)ing\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
12694
12956
  LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
12695
12957
  CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
12696
12958
  RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
@@ -12704,6 +12966,7 @@ var init_readiness_poll = __esm({
12704
12966
  TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
12705
12967
  TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
12706
12968
  POLL_OPEN_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}async\b|\bexpect\.poll\s{0,20}\(|\bfor\s{0,20}\(|\bwhile\s{0,20}\(|\bdo\s{0,20}\{/g;
12969
+ PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
12707
12970
  PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
12708
12971
  POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
12709
12972
  }
@@ -13105,6 +13368,15 @@ function computes(line) {
13105
13368
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
13106
13369
  return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
13107
13370
  }
13371
+ function identifiersIn(line) {
13372
+ const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
13373
+ return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
13374
+ }
13375
+ function addTyped(typed, value) {
13376
+ const d = digitsOf(value);
13377
+ if (/\d/.test(d))
13378
+ typed.add(d);
13379
+ }
13108
13380
  function reasonFor4(kind, label, value) {
13109
13381
  const what = kind === "derived-value-shape-only" ? `The ${label} is checked only for its shape, so any amount passes, including a wrongly computed one.` : `The ${label} is checked against a number written into the test (${value}). That number was read off the page, so when the page computes it wrong the check still passes.`;
13110
13382
  return `${what} Instead, ${COMPUTE_SHAPE} If the number is not computed from other values (a list price, an id, a fixed fee), leave it as it is. Keep the existing checks.`;
@@ -13116,25 +13388,32 @@ function analyzeDerivedValues(rawCode) {
13116
13388
  for (const body of testBodies(rawCode)) {
13117
13389
  if (findings.length >= MAX_FINDINGS4)
13118
13390
  break;
13119
- if (body.some(computes))
13120
- continue;
13121
13391
  const typed = /* @__PURE__ */ new Set();
13122
13392
  const definitions = /* @__PURE__ */ new Map();
13393
+ const computed = /* @__PURE__ */ new Set();
13394
+ const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
13123
13395
  for (const line of body) {
13124
13396
  if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
13125
- for (const m of line.matchAll(/['"`]([^'"`\n]{0,40})['"`]/g)) {
13126
- const d = digitsOf(m[1]);
13127
- if (/\d/.test(d))
13128
- typed.add(d);
13129
- }
13397
+ for (const m of line.matchAll(QUOTED_RE))
13398
+ addTyped(typed, m[1]);
13399
+ const arg = FILL_ARG_RE.exec(line)?.[1];
13400
+ const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
13401
+ const value = literal?.slice(1).find((v) => v !== void 0);
13402
+ if (value !== void 0)
13403
+ addTyped(typed, value);
13130
13404
  }
13131
13405
  const def = DEFINITION_RE.exec(line);
13132
- if (def)
13406
+ if (def) {
13133
13407
  definitions.set(def[1], def[2]);
13408
+ if (computes(def[2]) || usesComputed(def[2]))
13409
+ computed.add(def[1]);
13410
+ }
13134
13411
  }
13135
13412
  for (const line of body) {
13136
13413
  if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
13137
13414
  continue;
13415
+ if (computes(line) || usesComputed(line))
13416
+ continue;
13138
13417
  const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
13139
13418
  const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
13140
13419
  if (!label)
@@ -13164,7 +13443,7 @@ function formatDerivedValueAdvisory(findings) {
13164
13443
  const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
13165
13444
  return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
13166
13445
  }
13167
- var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, COMPUTE_SHAPE;
13446
+ var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, TEMPLATE_EXPR_RE, IDENTIFIER_RE, QUOTED_RE, FILL_ARG_RE, LITERAL_VALUE_RE, COMPUTE_SHAPE;
13168
13447
  var init_derived_value_check = __esm({
13169
13448
  "../shared/dist/derived-value-check.js"() {
13170
13449
  "use strict";
@@ -13184,6 +13463,11 @@ var init_derived_value_check = __esm({
13184
13463
  DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
13185
13464
  EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
13186
13465
  LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
13466
+ TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
13467
+ IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
13468
+ QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
13469
+ FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
13470
+ LITERAL_VALUE_RE = /^\s{0,10}(?:'([^'\n]{0,40})'|"([^"\n]{0,40})"|`([^`$\n]{0,40})`|(\d[\d_,]{0,15}(?:\.\d{1,4})?))\s{0,10};?\s{0,10}$/;
13187
13471
  COMPUTE_SHAPE = "compute the expected value in the test from its inputs (the values the test typed, or each input read from the page) and compare: const toNumber = (s) => Number(s.replace(/[^\\d.-]/g, '')); const unit = toNumber(await <unit price locator>.innerText()); then expect(toNumber(await <shown value locator>.innerText())).toBeCloseTo(unit * quantity, 2); for several lines, read them all and sum them with reduce. For a comma-decimal price like \u20AC1.234,56, drop the dots and turn the comma into a point before Number, since the replace above would read it as 1.23456.";
13188
13472
  }
13189
13473
  });
@@ -15218,6 +15502,99 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
15218
15502
  const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
15219
15503
  return `stale-after-submit: ${described.slice(0, 3).join("; ")}${more}; these pass before the request lands and still pass if the server rejects or ignores the save. Wait for the result instead: page.waitForResponse around the click, or assert something the submit creates (the new row, the confirmation message, a path with an end anchor such as /\\/orders\\/\\d+$/)`;
15220
15504
  }
15505
+ function regexLiteralEnd4(text, at) {
15506
+ let inClass = false;
15507
+ for (let i = at + 1; i < text.length; i++) {
15508
+ const ch = text[i];
15509
+ if (ch === "\n")
15510
+ return -1;
15511
+ if (ch === "\\") {
15512
+ i++;
15513
+ continue;
15514
+ }
15515
+ if (inClass) {
15516
+ if (ch === "]")
15517
+ inClass = false;
15518
+ continue;
15519
+ }
15520
+ if (ch === "[") {
15521
+ inClass = true;
15522
+ continue;
15523
+ }
15524
+ if (ch === "/")
15525
+ return i + 1;
15526
+ }
15527
+ return -1;
15528
+ }
15529
+ function startsWithRegexLiteral(text) {
15530
+ const t = text.trimStart();
15531
+ return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
15532
+ }
15533
+ function afterFirstArg(args) {
15534
+ let depth = 0;
15535
+ let quote = null;
15536
+ for (let i = 0; i < args.length; i++) {
15537
+ const ch = args[i];
15538
+ if (quote) {
15539
+ if (ch === "\\") {
15540
+ i++;
15541
+ continue;
15542
+ }
15543
+ if (ch === quote)
15544
+ quote = null;
15545
+ continue;
15546
+ }
15547
+ if (ch === '"' || ch === "'" || ch === "`") {
15548
+ quote = ch;
15549
+ continue;
15550
+ }
15551
+ if (ch === "(" || ch === "[" || ch === "{")
15552
+ depth++;
15553
+ else if (ch === ")" || ch === "]" || ch === "}")
15554
+ depth--;
15555
+ else if (ch === "," && depth === 0)
15556
+ return args.slice(i + 1);
15557
+ }
15558
+ return "";
15559
+ }
15560
+ function analyzeRegexMatchers(rawCode) {
15561
+ if (!rawCode || typeof rawCode !== "string")
15562
+ return [];
15563
+ const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
15564
+ const out = [];
15565
+ const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
15566
+ let m;
15567
+ let examined = 0;
15568
+ let line = 1;
15569
+ let lineAt2 = 0;
15570
+ while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
15571
+ const subj = readBalanced(code, m.index + m[0].length);
15572
+ if (!subj)
15573
+ continue;
15574
+ const mm = /^\s{0,20}(\.\s{0,20}not)?\s{0,20}\.\s{0,20}(\w{1,40})\s{0,20}\(/.exec(code.slice(subj.end + 1, subj.end + 1 + 80));
15575
+ if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
15576
+ continue;
15577
+ const args = readBalanced(code, subj.end + 1 + mm[0].length);
15578
+ if (!args)
15579
+ continue;
15580
+ const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
15581
+ if (!startsWithRegexLiteral(expected))
15582
+ continue;
15583
+ if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain" && (startsWithRegexLiteral(subj.text) || /^new\s+RegExp\b/.test(subj.text.trim())))
15584
+ continue;
15585
+ for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
15586
+ line++;
15587
+ lineAt2 = i + 1;
15588
+ }
15589
+ out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
15590
+ }
15591
+ return out;
15592
+ }
15593
+ function formatRegexMatcherNote(findings) {
15594
+ const lines = findings.map((f) => `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? "always passes (a regex never equals the value), so it checks nothing" : "can never pass: this matcher compares by equality and a regex never equals a value"}.`);
15595
+ return `${lines.join("\n")}
15596
+ A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For a DOM property read it first: expect(await loc.evaluate((n) => n.<prop>)).toMatch(/re/). Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
15597
+ }
15221
15598
  function lintTestQuality(rawCode, opts = {}) {
15222
15599
  if (!rawCode || typeof rawCode !== "string")
15223
15600
  return [];
@@ -15225,6 +15602,10 @@ function lintTestQuality(rawCode, opts = {}) {
15225
15602
  return lintTestData(rawCode, { pageContacts: opts.pageContacts });
15226
15603
  if (opts.only === "test-data-blocking")
15227
15604
  return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
15605
+ if (opts.only === "regex-matcher") {
15606
+ const found = analyzeRegexMatchers(rawCode);
15607
+ return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
15608
+ }
15228
15609
  const warnings = [];
15229
15610
  if (rawCode.length > MAX_LINT_INPUT) {
15230
15611
  warnings.push(`The code is ${Math.round(rawCode.length / 1024)} KB; the quality lint checked only the first ${MAX_LINT_INPUT / 1e3} KB. Split it into smaller tests (one behaviour per test).`);
@@ -15499,7 +15880,7 @@ function specItemsFromTestPlans(testPlans) {
15499
15880
  }
15500
15881
  return [...new Set(out)].slice(0, 200);
15501
15882
  }
15502
- var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, STOP, SYNONYMS, AREA_TOKENS;
15883
+ var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, MAX_REGEX_MATCHER_FINDINGS, REGEX_EQUALITY_MATCHERS, STOP, SYNONYMS, AREA_TOKENS;
15503
15884
  var init_test_authoring_quality = __esm({
15504
15885
  "../shared/dist/test-authoring-quality.js"() {
15505
15886
  "use strict";
@@ -15731,6 +16112,8 @@ var init_test_authoring_quality = __esm({
15731
16112
  };
15732
16113
  LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
15733
16114
  BUNDLED_CHECKS_BEFORE_WRITE = 5;
16115
+ MAX_REGEX_MATCHER_FINDINGS = 10;
16116
+ REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
15734
16117
  STOP = /* @__PURE__ */ new Set([
15735
16118
  "a",
15736
16119
  "an",
@@ -15959,6 +16342,19 @@ var init_fence_runs = __esm({
15959
16342
  }
15960
16343
  });
15961
16344
 
16345
+ // ../shared/dist/trailing-slashes.js
16346
+ function stripTrailingSlashes(value) {
16347
+ let end = value.length;
16348
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
16349
+ end--;
16350
+ return end === value.length ? value : value.slice(0, end);
16351
+ }
16352
+ var init_trailing_slashes = __esm({
16353
+ "../shared/dist/trailing-slashes.js"() {
16354
+ "use strict";
16355
+ }
16356
+ });
16357
+
15962
16358
  // ../shared/dist/survey-bug-signature.js
15963
16359
  function normalizeWords(text) {
15964
16360
  return text.toLowerCase().replace(UUID, " id ").replace(PREFIXED_ID, " id ").replace(HEX_RUN, (run) => /\d/.test(run) ? " id " : run).replace(NUMBER_RUN, " # ").replace(/[^\p{L}\p{N}#]+/gu, " ").replace(/\s+/g, " ").trim();
@@ -15969,7 +16365,7 @@ function normalizeBugPath(route) {
15969
16365
  if (hash >= 0 && path[hash + 1] !== "/")
15970
16366
  path = path.slice(0, hash);
15971
16367
  const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
15972
- const joined = segments.join("/").replace(/\/+$/, "");
16368
+ const joined = stripTrailingSlashes(segments.join("/"));
15973
16369
  return joined || (path.startsWith("/") ? "/" : "");
15974
16370
  }
15975
16371
  function isGenericConsoleMessage(message) {
@@ -16103,6 +16499,7 @@ var SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_
16103
16499
  var init_survey_bug_signature = __esm({
16104
16500
  "../shared/dist/survey-bug-signature.js"() {
16105
16501
  "use strict";
16502
+ init_trailing_slashes();
16106
16503
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16107
16504
  UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
16108
16505
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -17383,9 +17780,9 @@ function lastLocatorLabels(chain) {
17383
17780
  if (t)
17384
17781
  labels.push(t[1]);
17385
17782
  }
17386
- const has = optionText(last.args, HAS_TEXT_OPTION_RE);
17387
- if (has !== null)
17388
- labels.push(has);
17783
+ const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
17784
+ if (has2 !== null)
17785
+ labels.push(has2);
17389
17786
  }
17390
17787
  return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
17391
17788
  }
@@ -18142,14 +18539,17 @@ var init_promotion_quality_gate = __esm({
18142
18539
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18143
18540
  strengthCheck = {
18144
18541
  id: "assertion-strength",
18145
- run({ code, strengthGate, flagContradictedAdditions }) {
18542
+ run({ code, tags, strengthGate, flagContradictedAdditions }) {
18146
18543
  const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
18147
18544
  if (verdict.severity === "route-bug")
18148
18545
  return [{ check: "route-bug" }];
18149
- if (verdict.severity !== "block")
18546
+ const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
18547
+ if (verdict.severity !== "block" && !authDowngrade)
18150
18548
  return [];
18151
18549
  const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
18152
- return revealed ? [] : [{ check: "strength-block" }];
18550
+ if (revealed)
18551
+ return [];
18552
+ return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
18153
18553
  }
18154
18554
  };
18155
18555
  STRING_CONST = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,60})\s{0,20}=\s{0,20}(?:'([^'\\\n]{0,300})'|"([^"\\\n]{0,300})"|`([^`\\$\n]{0,300})`)\s{0,20}[;\n]/g;
@@ -18556,6 +18956,7 @@ __export(dist_exports, {
18556
18956
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
18557
18957
  AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
18558
18958
  AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
18959
+ AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
18559
18960
  AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
18560
18961
  AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
18561
18962
  AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
@@ -18688,6 +19089,8 @@ __export(dist_exports, {
18688
19089
  LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
18689
19090
  LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
18690
19091
  LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
19092
+ MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
19093
+ MAX_CALLS: () => MAX_CALLS,
18691
19094
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
18692
19095
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
18693
19096
  MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
@@ -18732,6 +19135,7 @@ __export(dist_exports, {
18732
19135
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
18733
19136
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
18734
19137
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
19138
+ OPENCODE_GO_2_PREFIX: () => OPENCODE_GO_2_PREFIX,
18735
19139
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
18736
19140
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
18737
19141
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -18767,6 +19171,8 @@ __export(dist_exports, {
18767
19171
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
18768
19172
  PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
18769
19173
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
19174
+ PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
19175
+ PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
18770
19176
  PUBLIC_PLANS: () => PUBLIC_PLANS,
18771
19177
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
18772
19178
  READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
@@ -18805,6 +19211,7 @@ __export(dist_exports, {
18805
19211
  SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
18806
19212
  SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
18807
19213
  SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
19214
+ TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
18808
19215
  TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
18809
19216
  TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
18810
19217
  TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
@@ -18820,6 +19227,8 @@ __export(dist_exports, {
18820
19227
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
18821
19228
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
18822
19229
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
19230
+ ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
19231
+ ZAI_PREFIX: () => ZAI_PREFIX,
18823
19232
  accumulateStatements: () => accumulateStatements,
18824
19233
  adoptBrokerFailover: () => adoptBrokerFailover,
18825
19234
  aiCostToCreditCents: () => aiCostToCreditCents,
@@ -18828,6 +19237,7 @@ __export(dist_exports, {
18828
19237
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
18829
19238
  analyzePersistenceReread: () => analyzePersistenceReread,
18830
19239
  analyzeReadinessPolling: () => analyzeReadinessPolling,
19240
+ analyzeRegexMatchers: () => analyzeRegexMatchers,
18831
19241
  analyzeSharedAccountUse: () => analyzeSharedAccountUse,
18832
19242
  analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
18833
19243
  analyzeTestData: () => analyzeTestData,
@@ -18991,6 +19401,7 @@ __export(dist_exports, {
18991
19401
  formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
18992
19402
  formatPoolBudgetLine: () => formatPoolBudgetLine,
18993
19403
  formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
19404
+ formatRegexMatcherNote: () => formatRegexMatcherNote,
18994
19405
  formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
18995
19406
  formatSuspiciousExpectation: () => formatSuspiciousExpectation,
18996
19407
  formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
@@ -19029,12 +19440,14 @@ __export(dist_exports, {
19029
19440
  hasEncodedRequestBody: () => hasEncodedRequestBody,
19030
19441
  hasEngineSpecParts: () => hasEngineSpecParts,
19031
19442
  hasOnlyConstantAssertions: () => hasOnlyConstantAssertions,
19443
+ hasOpenCodeGo2Prefix: () => hasOpenCodeGo2Prefix,
19032
19444
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19033
19445
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19034
19446
  hasPlatformLocator: () => hasPlatformLocator,
19035
19447
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19036
19448
  hasSurveyNotes: () => hasSurveyNotes,
19037
19449
  hasTestFailContract: () => hasTestFailContract,
19450
+ hasZaiPrefix: () => hasZaiPrefix,
19038
19451
  healMobileTestSteps: () => healMobileTestSteps,
19039
19452
  healthRunWhere: () => healthRunWhere,
19040
19453
  heldCodeRecord: () => heldCodeRecord,
@@ -19138,6 +19551,7 @@ __export(dist_exports, {
19138
19551
  isValidSttProvider: () => isValidSttProvider,
19139
19552
  isValidTtsModel: () => isValidTtsModel,
19140
19553
  isValidTtsVoiceId: () => isValidTtsVoiceId,
19554
+ isZaiModelId: () => isZaiModelId,
19141
19555
  jsEscapedForms: () => jsEscapedForms,
19142
19556
  jsonStructureRefusalOf: () => jsonStructureRefusalOf,
19143
19557
  jsonStructureVerify: () => jsonStructureVerify,
@@ -19186,6 +19600,7 @@ __export(dist_exports, {
19186
19600
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
19187
19601
  parseAppTimezone: () => parseAppTimezone,
19188
19602
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
19603
+ parseAuthSessionStamp: () => parseAuthSessionStamp,
19189
19604
  parseBoolFlag: () => parseBoolFlag,
19190
19605
  parseDbPoolSize: () => parseDbPoolSize,
19191
19606
  parseDeepgramListenResponse: () => parseDeepgramListenResponse,
@@ -19229,7 +19644,11 @@ __export(dist_exports, {
19229
19644
  profileKeyRoute: () => profileKeyRoute,
19230
19645
  projectAppHosts: () => projectAppHosts,
19231
19646
  providerChainFor: () => providerChainFor,
19647
+ providerChainHostOf: () => providerChainHostOf,
19648
+ providerChainLeadsFor: () => providerChainLeadsFor,
19232
19649
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
19650
+ providerHostPlan: () => providerHostPlan,
19651
+ readBalanced: () => readBalanced,
19233
19652
  readChatFailover: () => readChatFailover,
19234
19653
  readChatReasoningTokens: () => readChatReasoningTokens,
19235
19654
  readChatUsage: () => readChatUsage,
@@ -19312,6 +19731,7 @@ __export(dist_exports, {
19312
19731
  stripCoverageComments: () => stripCoverageComments,
19313
19732
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
19314
19733
  stripLoginScaffolding: () => stripLoginScaffolding,
19734
+ stripTrailingSlashes: () => stripTrailingSlashes,
19315
19735
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
19316
19736
  sttModelsForProvider: () => sttModelsForProvider,
19317
19737
  summarizeHealth: () => summarizeHealth,
@@ -19339,7 +19759,8 @@ __export(dist_exports, {
19339
19759
  withReviewSource: () => withReviewSource,
19340
19760
  withoutNotChargedClaim: () => withoutNotChargedClaim,
19341
19761
  withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
19342
- withoutSurveyNotes: () => withoutSurveyNotes
19762
+ withoutSurveyNotes: () => withoutSurveyNotes,
19763
+ zaiWireModel: () => zaiWireModel
19343
19764
  });
19344
19765
  var init_dist = __esm({
19345
19766
  "../shared/dist/index.js"() {
@@ -19381,6 +19802,7 @@ var init_dist = __esm({
19381
19802
  init_admin_view_return_to();
19382
19803
  init_auth_plan();
19383
19804
  init_auth_block_detector();
19805
+ init_auth_session_stamp();
19384
19806
  init_auth_state_freshness();
19385
19807
  init_skip_coverage_detector();
19386
19808
  init_feature_coverage();
@@ -19437,6 +19859,7 @@ var init_dist = __esm({
19437
19859
  init_promotion_quality_gate();
19438
19860
  init_regex_alternation();
19439
19861
  init_page_evidence();
19862
+ init_trailing_slashes();
19440
19863
  }
19441
19864
  });
19442
19865
 
@@ -19470,12 +19893,7 @@ export {
19470
19893
  isAppOriginResolutionEnabled,
19471
19894
  resolveCrossOriginConfigOverride,
19472
19895
  deriveAuthPlan,
19473
- detectAuthBlock,
19474
- detectApiColdSessionBlock,
19475
- coldSessionConfigMessage,
19476
- detectSkippedApiCoverage,
19477
19896
  isSharedAuthInjectEnabled,
19478
- isAuthReadinessGuardEnabled,
19479
19897
  isRepositoryCli,
19480
19898
  repositoryCliLabel,
19481
19899
  dist_exports,