@fairfox/polly 0.82.1 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cli/polly.js +22 -1
  2. package/dist/cli/polly.js.map +3 -3
  3. package/dist/tools/bdd/src/args.d.ts +21 -0
  4. package/dist/tools/bdd/src/bus-driver.d.ts +36 -0
  5. package/dist/tools/bdd/src/check-verify.d.ts +15 -0
  6. package/dist/tools/bdd/src/cli.d.ts +2 -0
  7. package/dist/tools/bdd/src/cli.js +705 -0
  8. package/dist/tools/bdd/src/cli.js.map +19 -0
  9. package/dist/tools/bdd/src/config.d.ts +9 -0
  10. package/dist/tools/bdd/src/extract.d.ts +2 -0
  11. package/dist/tools/bdd/src/index.d.ts +19 -0
  12. package/dist/tools/bdd/src/index.js +544 -0
  13. package/dist/tools/bdd/src/index.js.map +17 -0
  14. package/dist/tools/bdd/src/parse.d.ts +3 -0
  15. package/dist/tools/bdd/src/report.d.ts +6 -0
  16. package/dist/tools/bdd/src/run.d.ts +8 -0
  17. package/dist/tools/bdd/src/scaffold.d.ts +7 -0
  18. package/dist/tools/bdd/src/steps.d.ts +55 -0
  19. package/dist/tools/bdd/src/types.d.ts +152 -0
  20. package/dist/tools/bdd/src/witness.d.ts +23 -0
  21. package/dist/tools/quality/src/cli.js +304 -15
  22. package/dist/tools/quality/src/cli.js.map +6 -4
  23. package/dist/tools/quality/src/index.d.ts +2 -0
  24. package/dist/tools/quality/src/index.js +309 -15
  25. package/dist/tools/quality/src/index.js.map +6 -4
  26. package/dist/tools/quality/src/no-fixed-waits.d.ts +52 -0
  27. package/dist/tools/quality/src/no-tautology-ensures.d.ts +67 -0
  28. package/dist/tools/quality/src/plugins/core.d.ts +1 -1
  29. package/dist/tools/test/src/tiers/cli.js +21 -1
  30. package/dist/tools/test/src/tiers/cli.js.map +3 -3
  31. package/dist/tools/verify/src/cli.js +569 -1
  32. package/dist/tools/verify/src/cli.js.map +8 -4
  33. package/package.json +7 -1
@@ -36,7 +36,9 @@ export { type CssVarsOptions, checkCssVars } from "./css/check-vars.ts";
36
36
  export type { CssCheckResult, CssViolation } from "./css/shared.ts";
37
37
  export { logger, type QualityLogger, resetLogger, } from "./logger.ts";
38
38
  export { type CheckResult, checkNoAsCasting, isLineClean, suggestFix, type Violation, } from "./no-as-casting";
39
+ export { checkNoFixedWaits, type FixedWaitViolation, type NoFixedWaitsOptions, type NoFixedWaitsResult, scanText, } from "./no-fixed-waits";
39
40
  export { checkNoRequire, isLineRequireClean, type NoRequireCheckOptions, type NoRequireCheckResult, type NoRequireViolation, } from "./no-require";
41
+ export { checkNoTautologyEnsures, type NoTautologyEnsuresOptions, type NoTautologyEnsuresResult, splitTopLevelArgs, type TautologyViolation, tautologyReason, } from "./no-tautology-ensures";
40
42
  export { type CheckGitignoreOptions, type CheckSecretsOptions, checkGitignoreCoversAllowlist, checkSecrets, type GitignoreCheckResult, type SecretsCheckResult, } from "./secrets.ts";
41
43
  export { type AttestOptions, type AttestResult, digestRun, runAttest, summariseRunReport, } from "./attest";
42
44
  export { type CacheEntry, type CacheInputs, computeInputsHash, getCachedOutcome, setCachedOutcome, } from "./cache";
@@ -841,9 +841,109 @@ async function checkNoAsCasting(options) {
841
841
  }
842
842
  return { violations, print: () => printViolations(violations) };
843
843
  }
844
- // tools/quality/src/no-require.ts
844
+ // tools/quality/src/no-fixed-waits.ts
845
845
  import { readFileSync as readFileSync2 } from "node:fs";
846
846
  import { Glob as Glob2 } from "bun";
847
+ var HINT = "wait on a real condition (poll loop, web-first assertion, microtask flush) or a sanctioned delay primitive";
848
+ var RULES = [
849
+ { pattern: /\.waitForTimeout\s*\(/, message: `waitForTimeout is a fixed sleep — ${HINT}` },
850
+ { pattern: /\bBun\.sleep\s*\(/, message: `Bun.sleep is a fixed sleep — ${HINT}` },
851
+ {
852
+ pattern: /\bsetTimeout\s*\(\s*(?:resolve|r|res|done)\s*,/,
853
+ message: `setTimeout resolving a promise is a fixed sleep — ${HINT}`
854
+ },
855
+ {
856
+ pattern: /\bsetTimeout\s*\(\s*\(\s*\)\s*=>\s*(?:resolve|r|res|done)\b/,
857
+ message: `setTimeout resolving a promise is a fixed sleep — ${HINT}`
858
+ },
859
+ {
860
+ pattern: /new\s+Promise\b.*\(\s*\(?\s*([A-Za-z_$][\w$]*)\s*\)?\s*=>.*\bsetTimeout\s*\(\s*\1\b/,
861
+ message: `Promise wrapping setTimeout is a fixed sleep — ${HINT}`
862
+ }
863
+ ];
864
+ var DEFAULT_EXCLUDE = ["node_modules", "dist", ".git", ".bun", "build", "coverage"];
865
+ function skipString(text, i) {
866
+ const quote = text[i];
867
+ if (quote !== '"' && quote !== "'" && quote !== "`")
868
+ return i;
869
+ let j = i + 1;
870
+ while (j < text.length && text[j] !== quote) {
871
+ if (text[j] === "\\")
872
+ j++;
873
+ j++;
874
+ }
875
+ return j;
876
+ }
877
+ function codeOnly(line) {
878
+ let out = "";
879
+ for (let i = 0;i < line.length; i++) {
880
+ const ch = line[i];
881
+ if (ch === '"' || ch === "'" || ch === "`") {
882
+ const end = skipString(line, i);
883
+ out += " ".repeat(end - i + 1);
884
+ i = end;
885
+ continue;
886
+ }
887
+ if (ch === "/" && line[i + 1] === "/")
888
+ break;
889
+ out += ch;
890
+ }
891
+ return out;
892
+ }
893
+ function scanText(content, filePath = "<text>") {
894
+ const violations = [];
895
+ const lines = content.split(`
896
+ `);
897
+ for (let i = 0;i < lines.length; i++) {
898
+ const line = lines[i] ?? "";
899
+ const trimmed = line.trim();
900
+ if (trimmed === "" || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
901
+ continue;
902
+ }
903
+ const code = codeOnly(line);
904
+ const rule = RULES.find((r) => r.pattern.test(code));
905
+ if (rule)
906
+ violations.push({ file: filePath, line: i + 1, content: trimmed, message: rule.message });
907
+ }
908
+ return violations;
909
+ }
910
+ function isExcluded(relative3, excludeDirs, excludeFiles) {
911
+ if (relative3.split("/").some((s) => excludeDirs.has(s)))
912
+ return true;
913
+ const normalized = relative3.replace(/\\/g, "/");
914
+ return excludeFiles.some((suffix) => normalized.endsWith(suffix));
915
+ }
916
+ function printViolations2(violations) {
917
+ if (violations.length === 0) {
918
+ logger.log("[no-fixed-waits] ✅ No violations found.");
919
+ return;
920
+ }
921
+ logger.log(`[no-fixed-waits] ❌ ${violations.length} fixed-duration wait(s) found:
922
+ `);
923
+ for (const v of violations) {
924
+ logger.log(` ${v.file}:${v.line}`);
925
+ logger.log(` ${v.content}`);
926
+ logger.log(` \uD83D\uDCA1 ${v.message}`);
927
+ logger.log("");
928
+ }
929
+ }
930
+ async function checkNoFixedWaits(options) {
931
+ const { rootDir } = options;
932
+ const excludeDirs = new Set(options.exclude ?? DEFAULT_EXCLUDE);
933
+ const excludeFiles = [...options.excludeFiles ?? [], "no-fixed-waits.ts"];
934
+ const glob = new Glob2(options.filePatterns ?? "**/*.{ts,tsx}");
935
+ const violations = [];
936
+ for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {
937
+ const relative3 = file.replace(`${rootDir}/`, "");
938
+ if (isExcluded(relative3, excludeDirs, excludeFiles))
939
+ continue;
940
+ violations.push(...scanText(readFileSync2(file, "utf-8"), relative3));
941
+ }
942
+ return { violations, print: () => printViolations2(violations) };
943
+ }
944
+ // tools/quality/src/no-require.ts
945
+ import { readFileSync as readFileSync3 } from "node:fs";
946
+ import { Glob as Glob3 } from "bun";
847
947
  function isLineRequireClean(line) {
848
948
  if (!line.includes("require")) {
849
949
  return true;
@@ -911,7 +1011,7 @@ async function checkNoRequire(options) {
911
1011
  const rootDir = options.rootDir;
912
1012
  const excludeDirs = new Set(options.exclude ?? ["node_modules", "dist", ".git", ".bun"]);
913
1013
  const pattern = options.filePatterns ?? "**/*.{ts,tsx}";
914
- const glob = new Glob2(pattern);
1014
+ const glob = new Glob3(pattern);
915
1015
  const violations = [];
916
1016
  for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {
917
1017
  const relative3 = file.replace(`${rootDir}/`, "");
@@ -919,7 +1019,7 @@ async function checkNoRequire(options) {
919
1019
  if (segments.some((s) => excludeDirs.has(s))) {
920
1020
  continue;
921
1021
  }
922
- const content = readFileSync2(file, "utf-8");
1022
+ const content = readFileSync3(file, "utf-8");
923
1023
  violations.push(...findRequireViolations(relative3, content));
924
1024
  }
925
1025
  return {
@@ -927,6 +1027,157 @@ async function checkNoRequire(options) {
927
1027
  print: () => printRequireViolations(violations)
928
1028
  };
929
1029
  }
1030
+ // tools/quality/src/no-tautology-ensures.ts
1031
+ import { readFileSync as readFileSync4 } from "node:fs";
1032
+ import { Glob as Glob4 } from "bun";
1033
+ var DEFAULT_PRIMITIVES = ["ensures", "requires"];
1034
+ var DEFAULT_EXCLUDE2 = [
1035
+ "node_modules",
1036
+ "dist",
1037
+ ".git",
1038
+ ".bun",
1039
+ "coverage",
1040
+ "specs",
1041
+ "tests",
1042
+ "__tests__",
1043
+ "examples"
1044
+ ];
1045
+ var LITERAL = /^(true|false|null|undefined|\d+(\.\d+)?|"[^"]*"|'[^']*'|`[^`]*`)$/;
1046
+ function tautologyReason(predicate) {
1047
+ const trimmed = predicate.trim();
1048
+ if (LITERAL.test(trimmed))
1049
+ return `literal '${trimmed}'`;
1050
+ const cmp = trimmed.match(/^(.+?)\s*(===|!==|==|!=)\s*(.+)$/);
1051
+ if (cmp) {
1052
+ const lhs = cmp[1]?.trim() ?? "";
1053
+ const rhs = cmp[3]?.trim() ?? "";
1054
+ if (LITERAL.test(lhs) && LITERAL.test(rhs))
1055
+ return `literal-vs-literal '${trimmed}'`;
1056
+ }
1057
+ return;
1058
+ }
1059
+ function skipString2(text, i) {
1060
+ const quote = text[i];
1061
+ if (quote !== '"' && quote !== "'" && quote !== "`")
1062
+ return i;
1063
+ let j = i + 1;
1064
+ while (j < text.length && text[j] !== quote) {
1065
+ if (text[j] === "\\")
1066
+ j++;
1067
+ j++;
1068
+ }
1069
+ return j;
1070
+ }
1071
+ function findClosingParen(text, openIdx) {
1072
+ let depth = 0;
1073
+ for (let i = openIdx;i < text.length; i++) {
1074
+ const ch = text[i];
1075
+ if (ch === "(") {
1076
+ depth++;
1077
+ } else if (ch === ")") {
1078
+ if (--depth === 0)
1079
+ return i;
1080
+ } else {
1081
+ i = skipString2(text, i);
1082
+ }
1083
+ }
1084
+ return -1;
1085
+ }
1086
+ function splitTopLevelArgs(args) {
1087
+ const parts = [];
1088
+ let depth = 0;
1089
+ let start = 0;
1090
+ for (let i = 0;i < args.length; i++) {
1091
+ const ch = args[i];
1092
+ if (ch === "(" || ch === "[" || ch === "{") {
1093
+ depth++;
1094
+ } else if (ch === ")" || ch === "]" || ch === "}") {
1095
+ depth--;
1096
+ } else if (ch === "," && depth === 0) {
1097
+ parts.push(args.slice(start, i));
1098
+ start = i + 1;
1099
+ } else {
1100
+ i = skipString2(args, i);
1101
+ }
1102
+ }
1103
+ parts.push(args.slice(start));
1104
+ return parts.map((p) => p.trim());
1105
+ }
1106
+ function isFileExcluded2(relative3, excludeDirs, excludeFiles) {
1107
+ const segments = relative3.split("/");
1108
+ if (segments.some((s) => excludeDirs.has(s)))
1109
+ return true;
1110
+ const basename = segments[segments.length - 1] ?? "";
1111
+ return excludeFiles.has(basename) || excludeFiles.has(relative3);
1112
+ }
1113
+ function isNonCall(line, lineBefore) {
1114
+ const isImport = /\b(import|export)\b/.test(line) && line.includes("{") && !line.includes(";");
1115
+ const isFrom = /\b(import|export)\b.*\bfrom\b/.test(line);
1116
+ return isImport || isFrom || lineBefore.includes("//");
1117
+ }
1118
+ function findViolations2(relative3, content, callPattern) {
1119
+ const results = [];
1120
+ callPattern.lastIndex = 0;
1121
+ let match = callPattern.exec(content);
1122
+ while (match !== null) {
1123
+ const current = match;
1124
+ match = callPattern.exec(content);
1125
+ const openIdx = current.index + current[0].length - 1;
1126
+ const lineStart = content.lastIndexOf(`
1127
+ `, current.index) + 1;
1128
+ const lineEnd = content.indexOf(`
1129
+ `, current.index);
1130
+ const line = content.slice(lineStart, lineEnd === -1 ? content.length : lineEnd);
1131
+ if (isNonCall(line, content.slice(lineStart, current.index)))
1132
+ continue;
1133
+ const closeIdx = findClosingParen(content, openIdx);
1134
+ if (closeIdx === -1)
1135
+ continue;
1136
+ const predicate = splitTopLevelArgs(content.slice(openIdx + 1, closeIdx))[0];
1137
+ const reason = predicate ? tautologyReason(predicate) : undefined;
1138
+ if (reason === undefined)
1139
+ continue;
1140
+ results.push({
1141
+ file: relative3,
1142
+ line: content.slice(0, current.index).split(`
1143
+ `).length,
1144
+ content: line.trim(),
1145
+ reason: `${current[1] ?? "ensures"}(...) predicate is a ${reason}`
1146
+ });
1147
+ }
1148
+ return results;
1149
+ }
1150
+ function printViolations3(violations) {
1151
+ if (violations.length === 0) {
1152
+ logger.log("[no-tautology-ensures] ✅ No violations found.");
1153
+ return;
1154
+ }
1155
+ logger.log(`[no-tautology-ensures] ❌ ${violations.length} violation(s) found:
1156
+ `);
1157
+ for (const v of violations) {
1158
+ logger.log(` ${v.file}:${v.line}`);
1159
+ logger.log(` ${v.content}`);
1160
+ logger.log(` \uD83D\uDCA1 ${v.reason}`);
1161
+ logger.log("");
1162
+ }
1163
+ logger.log("[no-tautology-ensures] A verify predicate must reference state — a literal asserts nothing.");
1164
+ }
1165
+ async function checkNoTautologyEnsures(options) {
1166
+ const { rootDir } = options;
1167
+ const excludeDirs = new Set(options.exclude ?? DEFAULT_EXCLUDE2);
1168
+ const excludeFiles = new Set([...options.excludeFiles ?? [], "no-tautology-ensures.ts"]);
1169
+ const primitives = options.primitives ?? DEFAULT_PRIMITIVES;
1170
+ const callPattern = new RegExp(`\\b(${primitives.join("|")})\\s*\\(`, "g");
1171
+ const glob = new Glob4(options.filePatterns ?? "**/*.{ts,tsx}");
1172
+ const violations = [];
1173
+ for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {
1174
+ const relative3 = file.replace(`${rootDir}/`, "");
1175
+ if (isFileExcluded2(relative3, excludeDirs, excludeFiles))
1176
+ continue;
1177
+ violations.push(...findViolations2(relative3, readFileSync4(file, "utf-8"), callPattern));
1178
+ }
1179
+ return { violations, print: () => printViolations3(violations) };
1180
+ }
930
1181
  // tools/quality/src/secrets.ts
931
1182
  import { readFile } from "node:fs/promises";
932
1183
  import { join as join3 } from "node:path";
@@ -1462,7 +1713,7 @@ import { join as join11 } from "node:path";
1462
1713
 
1463
1714
  // tools/quality/src/plugins/core.ts
1464
1715
  import { join as join10 } from "node:path";
1465
- import { Glob as Glob5 } from "bun";
1716
+ import { Glob as Glob7 } from "bun";
1466
1717
 
1467
1718
  // tools/quality/src/plugins/cliche-checks.ts
1468
1719
  import { readdir as readdir3 } from "node:fs/promises";
@@ -1885,7 +2136,7 @@ var additionalCoreChecks = [
1885
2136
  // tools/quality/src/plugins/extra-checks.ts
1886
2137
  import { readdir as readdir5 } from "node:fs/promises";
1887
2138
  import { join as join8, relative as relative5 } from "node:path";
1888
- import { Glob as Glob3 } from "bun";
2139
+ import { Glob as Glob5 } from "bun";
1889
2140
  var SKIP_DIRS_DEFAULT2 = ["node_modules", ".git", "dist", ".bun", ".cache"];
1890
2141
  function isCommentLine2(trimmed) {
1891
2142
  return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*");
@@ -2023,7 +2274,7 @@ function buildHookRegex(banned) {
2023
2274
  }
2024
2275
  function isAllowedByGlob(rel, allowedFiles) {
2025
2276
  for (const pattern of allowedFiles) {
2026
- if (new Glob3(pattern).match(rel))
2277
+ if (new Glob5(pattern).match(rel))
2027
2278
  return true;
2028
2279
  }
2029
2280
  return false;
@@ -2090,7 +2341,7 @@ var DEFAULT_TYPOGRAPHIC = {
2090
2341
  };
2091
2342
  function fileMatchesAnyGlob(rel, globs) {
2092
2343
  for (const g of globs) {
2093
- if (new Glob3(g).match(rel))
2344
+ if (new Glob5(g).match(rel))
2094
2345
  return true;
2095
2346
  }
2096
2347
  return false;
@@ -2158,7 +2409,7 @@ var extraCoreChecks = [
2158
2409
  // tools/quality/src/plugins/import-checks.ts
2159
2410
  import { readdir as readdir6 } from "node:fs/promises";
2160
2411
  import { join as join9, relative as relative6 } from "node:path";
2161
- import { Glob as Glob4 } from "bun";
2412
+ import { Glob as Glob6 } from "bun";
2162
2413
  var SKIP_DIRS_DEFAULT3 = ["node_modules", ".git", "dist", ".bun", ".cache"];
2163
2414
  function isCommentLine3(trimmed) {
2164
2415
  return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*");
@@ -2185,7 +2436,7 @@ async function walkScannableFiles2(dir, skipDirs, out) {
2185
2436
  }
2186
2437
  function isAllowedByGlob2(rel, globs) {
2187
2438
  for (const g of globs) {
2188
- if (new Glob4(g).match(rel))
2439
+ if (new Glob6(g).match(rel))
2189
2440
  return true;
2190
2441
  }
2191
2442
  return false;
@@ -2413,7 +2664,7 @@ async function runTscFor(packagePath, rootDir) {
2413
2664
  async function findWorkspaces(rootDir, patterns) {
2414
2665
  const out = [];
2415
2666
  for (const pattern of patterns) {
2416
- const glob = new Glob4(`${pattern}/tsconfig.json`);
2667
+ const glob = new Glob6(`${pattern}/tsconfig.json`);
2417
2668
  for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {
2418
2669
  out.push(file.replace(/\/tsconfig\.json$/, ""));
2419
2670
  }
@@ -2471,7 +2722,7 @@ async function scanFiles(rootDir, cfg) {
2471
2722
  const excludePackages = new Set(cfg.excludePackages ?? []);
2472
2723
  const excludeFiles = new Set(cfg.excludeFiles ?? []);
2473
2724
  const pattern = cfg.filePatterns ?? "**/*.{ts,tsx}";
2474
- const glob = new Glob5(pattern);
2725
+ const glob = new Glob7(pattern);
2475
2726
  const out = [];
2476
2727
  for await (const file of glob.scan({ cwd: rootDir, absolute: true })) {
2477
2728
  const rel = file.replace(`${rootDir}/`, "");
@@ -2526,6 +2777,42 @@ var noRequire = {
2526
2777
  };
2527
2778
  }
2528
2779
  };
2780
+ var noTautologyEnsures = {
2781
+ id: "polly:no-tautology-ensures",
2782
+ description: "Ban ensures/requires whose predicate is a literal (asserts nothing)",
2783
+ filesRead: (cfg, root) => scanFiles(root, resolveScanConfig(cfg)),
2784
+ run: async ({ rootDir, config }) => {
2785
+ const cfg = resolveScanConfig(config);
2786
+ const result = await checkNoTautologyEnsures({
2787
+ rootDir,
2788
+ ...cfg.exclude ? { exclude: cfg.exclude } : {},
2789
+ ...cfg.excludeFiles ? { excludeFiles: cfg.excludeFiles } : {},
2790
+ ...cfg.filePatterns ? { filePatterns: cfg.filePatterns } : {}
2791
+ });
2792
+ return {
2793
+ ok: result.violations.length === 0,
2794
+ messages: result.violations.map((v) => `${v.file}:${v.line}: ${v.content} — ${v.reason}`)
2795
+ };
2796
+ }
2797
+ };
2798
+ var noFixedWaits = {
2799
+ id: "polly:no-fixed-waits",
2800
+ description: "Ban fixed-duration sleeps (waitForTimeout / Bun.sleep / promise-wrapped setTimeout)",
2801
+ filesRead: (cfg, root) => scanFiles(root, resolveScanConfig(cfg)),
2802
+ run: async ({ rootDir, config }) => {
2803
+ const cfg = resolveScanConfig(config);
2804
+ const result = await checkNoFixedWaits({
2805
+ rootDir,
2806
+ exclude: cfg.exclude ?? DEFAULT_EXCLUDES,
2807
+ ...cfg.excludeFiles ? { excludeFiles: cfg.excludeFiles } : {},
2808
+ ...cfg.filePatterns ? { filePatterns: cfg.filePatterns } : {}
2809
+ });
2810
+ return {
2811
+ ok: result.violations.length === 0,
2812
+ messages: result.violations.map((v) => `${v.file}:${v.line}: ${v.content} — ${v.message}`)
2813
+ };
2814
+ }
2815
+ };
2529
2816
  var secrets = {
2530
2817
  id: "polly:secrets",
2531
2818
  description: "Run `gitleaks detect` against the working tree",
@@ -2587,13 +2874,15 @@ var gitignoreCrossCheck = {
2587
2874
  };
2588
2875
  }
2589
2876
  };
2590
- var POLLY_CORE_VERSION = "0.48.0";
2877
+ var POLLY_CORE_VERSION = "0.49.0";
2591
2878
  var pollyCorePlugin = {
2592
2879
  name: "polly",
2593
2880
  version: POLLY_CORE_VERSION,
2594
2881
  checks: [
2595
2882
  noAsCasting,
2596
2883
  noRequire,
2884
+ noTautologyEnsures,
2885
+ noFixedWaits,
2597
2886
  secrets,
2598
2887
  gitignoreCrossCheck,
2599
2888
  ...additionalCoreChecks,
@@ -2624,14 +2913,14 @@ async function loadQualityConfig(rootDir, explicitPath) {
2624
2913
  // tools/quality/src/plugins/polly-ui.ts
2625
2914
  import { readdir as readdir7 } from "node:fs/promises";
2626
2915
  import { join as join12, relative as relative7 } from "node:path";
2627
- import { Glob as Glob6 } from "bun";
2916
+ import { Glob as Glob8 } from "bun";
2628
2917
  var SKIP_DIRS_DEFAULT4 = ["node_modules", ".git", "dist", ".bun", ".cache"];
2629
2918
  function isCommentLine4(trimmed) {
2630
2919
  return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*");
2631
2920
  }
2632
2921
  function isAllowedByGlob3(rel, globs) {
2633
2922
  for (const g of globs) {
2634
- if (new Glob6(g).match(rel))
2923
+ if (new Glob8(g).match(rel))
2635
2924
  return true;
2636
2925
  }
2637
2926
  return false;
@@ -2823,9 +3112,12 @@ var pollyUiPlugin = {
2823
3112
  };
2824
3113
  export {
2825
3114
  validateRunConfig,
3115
+ tautologyReason,
2826
3116
  summariseRunReport,
2827
3117
  suggestFix,
3118
+ splitTopLevelArgs,
2828
3119
  setCachedOutcome,
3120
+ scanText,
2829
3121
  runChecks,
2830
3122
  runAttest,
2831
3123
  resetLogger,
@@ -2842,7 +3134,9 @@ export {
2842
3134
  computeInputsHash,
2843
3135
  checkSharedComponents,
2844
3136
  checkSecrets,
3137
+ checkNoTautologyEnsures,
2845
3138
  checkNoRequire,
3139
+ checkNoFixedWaits,
2846
3140
  checkNoAsCasting,
2847
3141
  checkGitignoreCoversAllowlist,
2848
3142
  checkCssVars,
@@ -2854,4 +3148,4 @@ export {
2854
3148
  DEFAULT_SHARED_COMPONENT_RULES
2855
3149
  };
2856
3150
 
2857
- //# debugId=DB1C98BCB50FC2FD64756E2164756E21
3151
+ //# debugId=553633575181BC3064756E2164756E21