aislop 0.9.6 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.js CHANGED
@@ -263,6 +263,218 @@ const loadConfig = (directory) => {
263
263
  }
264
264
  };
265
265
 
266
+ //#endregion
267
+ //#region src/utils/source-masker.ts
268
+ const JS_EXTS$2 = new Set([
269
+ ".ts",
270
+ ".tsx",
271
+ ".js",
272
+ ".jsx",
273
+ ".mjs",
274
+ ".cjs"
275
+ ]);
276
+ const PY_EXTS = new Set([".py"]);
277
+ const RB_EXTS = new Set([".rb"]);
278
+ const PHP_EXTS = new Set([".php"]);
279
+ const familyForExt = (ext) => {
280
+ if (JS_EXTS$2.has(ext)) return "js";
281
+ if (PY_EXTS.has(ext)) return "py";
282
+ if (RB_EXTS.has(ext)) return "rb";
283
+ if (PHP_EXTS.has(ext)) return "php";
284
+ return "none";
285
+ };
286
+ const maskStringsAndComments = (content, ext) => {
287
+ const family = familyForExt(ext);
288
+ if (family === "none") return content;
289
+ if (family === "js") return maskJs(content, true);
290
+ return maskSimple(content, family, true);
291
+ };
292
+ const maskComments = (content, ext) => {
293
+ const family = familyForExt(ext);
294
+ if (family === "none") return content;
295
+ if (family === "js") return maskJs(content, false);
296
+ return maskSimple(content, family, false);
297
+ };
298
+ const handleQuotesAndComments = (content, i, tplStack, mask, maskStrings) => {
299
+ const len = content.length;
300
+ const c = content[i];
301
+ const next = content[i + 1];
302
+ if (c === "\"" || c === "'") {
303
+ const strStart = i;
304
+ const end = consumeQuotedString(content, i, c);
305
+ if (maskStrings) mask(strStart + 1, end - 1);
306
+ return {
307
+ handled: true,
308
+ nextI: end
309
+ };
310
+ }
311
+ if (c === "`") {
312
+ const scan = consumeTemplateString(content, i + 1);
313
+ if (maskStrings) mask(i + 1, scan.maskEnd);
314
+ if (scan.openedInterp) tplStack.push(0);
315
+ return {
316
+ handled: true,
317
+ nextI: scan.resumeAt
318
+ };
319
+ }
320
+ if (c === "/" && next === "/") {
321
+ const strStart = i;
322
+ let k = i;
323
+ while (k < len && content[k] !== "\n") k++;
324
+ mask(strStart, k);
325
+ return {
326
+ handled: true,
327
+ nextI: k
328
+ };
329
+ }
330
+ if (c === "/" && next === "*") {
331
+ const strStart = i;
332
+ let k = i + 2;
333
+ while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
334
+ if (k < len - 1) k += 2;
335
+ mask(strStart, k);
336
+ return {
337
+ handled: true,
338
+ nextI: k
339
+ };
340
+ }
341
+ return {
342
+ handled: false,
343
+ nextI: i
344
+ };
345
+ };
346
+ const maskJs = (content, maskStrings) => {
347
+ const out = content.split("");
348
+ const len = content.length;
349
+ const tplStack = [];
350
+ let i = 0;
351
+ const mask = (start, end) => {
352
+ for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
353
+ };
354
+ while (i < len) {
355
+ const c = content[i];
356
+ if (tplStack.length > 0) {
357
+ if (c === "{") {
358
+ tplStack[tplStack.length - 1]++;
359
+ i++;
360
+ continue;
361
+ }
362
+ if (c === "}") {
363
+ if (tplStack[tplStack.length - 1] === 0) {
364
+ tplStack.pop();
365
+ const scan = consumeTemplateString(content, i + 1);
366
+ if (maskStrings) mask(i + 1, scan.maskEnd);
367
+ if (scan.openedInterp) tplStack.push(0);
368
+ i = scan.resumeAt;
369
+ continue;
370
+ }
371
+ tplStack[tplStack.length - 1]--;
372
+ i++;
373
+ continue;
374
+ }
375
+ }
376
+ const handled = handleQuotesAndComments(content, i, tplStack, mask, maskStrings);
377
+ if (handled.handled) {
378
+ i = handled.nextI;
379
+ continue;
380
+ }
381
+ i++;
382
+ }
383
+ return out.join("");
384
+ };
385
+ const consumeQuotedString = (content, start, quote) => {
386
+ const len = content.length;
387
+ let i = start + 1;
388
+ while (i < len) {
389
+ const c = content[i];
390
+ if (c === "\\" && i + 1 < len) {
391
+ i += 2;
392
+ continue;
393
+ }
394
+ if (c === quote) return i + 1;
395
+ if (c === "\n") return i;
396
+ i++;
397
+ }
398
+ return i;
399
+ };
400
+ const consumeTemplateString = (content, start) => {
401
+ const len = content.length;
402
+ let i = start;
403
+ while (i < len) {
404
+ const c = content[i];
405
+ if (c === "\\" && i + 1 < len) {
406
+ i += 2;
407
+ continue;
408
+ }
409
+ if (c === "`") return {
410
+ maskEnd: i,
411
+ resumeAt: i + 1,
412
+ openedInterp: false
413
+ };
414
+ if (c === "$" && content[i + 1] === "{") return {
415
+ maskEnd: i,
416
+ resumeAt: i + 2,
417
+ openedInterp: true
418
+ };
419
+ i++;
420
+ }
421
+ return {
422
+ maskEnd: i,
423
+ resumeAt: i,
424
+ openedInterp: false
425
+ };
426
+ };
427
+ const maskSimple = (content, family, maskStrings) => {
428
+ const out = content.split("");
429
+ const len = content.length;
430
+ let i = 0;
431
+ const mask = (start, end) => {
432
+ for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
433
+ };
434
+ while (i < len) {
435
+ const c = content[i];
436
+ const next = content[i + 1];
437
+ if (family === "py" && (c === "\"" || c === "'")) {
438
+ if (content[i + 1] === c && content[i + 2] === c) {
439
+ const triple = c + c + c;
440
+ const end = content.indexOf(triple, i + 3);
441
+ const stop = end === -1 ? len : end + 3;
442
+ if (maskStrings) mask(i + 3, stop - 3);
443
+ i = stop;
444
+ continue;
445
+ }
446
+ }
447
+ if (c === "\"" || c === "'") {
448
+ const strStart = i;
449
+ i = consumeQuotedString(content, i, c);
450
+ if (maskStrings) mask(strStart + 1, i - 1);
451
+ continue;
452
+ }
453
+ if ((family === "py" || family === "rb" || family === "php") && c === "#") {
454
+ const strStart = i;
455
+ while (i < len && content[i] !== "\n") i++;
456
+ mask(strStart, i);
457
+ continue;
458
+ }
459
+ if (family === "php" && c === "/" && next === "/") {
460
+ const strStart = i;
461
+ while (i < len && content[i] !== "\n") i++;
462
+ mask(strStart, i);
463
+ continue;
464
+ }
465
+ if (family === "php" && c === "/" && next === "*") {
466
+ const strStart = i;
467
+ i += 2;
468
+ while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
469
+ if (i < len - 1) i += 2;
470
+ mask(strStart, i);
471
+ continue;
472
+ }
473
+ i++;
474
+ }
475
+ return out.join("");
476
+ };
477
+
266
478
  //#endregion
267
479
  //#region src/utils/source-files.ts
268
480
  const MAX_BUFFER = 50 * 1024 * 1024;
@@ -513,7 +725,7 @@ const getSourceFilesWithExtras = (context, extraExtensions) => {
513
725
 
514
726
  //#endregion
515
727
  //#region src/engines/ai-slop/abstractions.ts
516
- const JS_EXTS$2 = new Set([
728
+ const JS_EXTS$1 = new Set([
517
729
  ".ts",
518
730
  ".tsx",
519
731
  ".js",
@@ -524,11 +736,11 @@ const JS_EXTS$2 = new Set([
524
736
  const THIN_WRAPPER_PATTERNS = [
525
737
  {
526
738
  pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
527
- extensions: JS_EXTS$2
739
+ extensions: JS_EXTS$1
528
740
  },
529
741
  {
530
742
  pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
531
- extensions: JS_EXTS$2
743
+ extensions: JS_EXTS$1
532
744
  },
533
745
  {
534
746
  pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
@@ -538,14 +750,16 @@ const THIN_WRAPPER_PATTERNS = [
538
750
  const AI_NAMING_PATTERNS = [/(?:helper|util|handler|process|do|handle|execute|perform)_?\d+/i, /(?:data|temp|result|value|item|obj|arr|str|num|val)\d+/];
539
751
  const FRAMEWORK_METHOD_NAMES = /^(?:setUp|tearDown|setUpClass|tearDownClass|setUpModule|tearDownModule)$/;
540
752
  const DUNDER_PATTERN = /^__\w+__$/;
541
- const hasHardcodedArgs = (matchText) => {
542
- const innerCallMatch = matchText.match(/=>\s*\w+\(([^)]*)\)\s*;?\s*$/);
543
- if (!innerCallMatch) {
544
- const returnCallMatch = matchText.match(/return\s+\w+\(([^)]*)\)\s*;?\s*\}/);
545
- if (!returnCallMatch) return false;
546
- return /['"`]\w+['"`]|(?<!\w)\d+(?!\w)/.test(returnCallMatch[1]);
547
- }
548
- return /['"`]\w+['"`]|(?<!\w)\d+(?!\w)/.test(innerCallMatch[1]);
753
+ const stripParam = (p) => p.trim().split(/[:=]/)[0].trim().replace(/^[*&]+/, "");
754
+ const paramNames = (paramsText) => new Set(paramsText.split(",").map(stripParam).filter((p) => p && p !== "self" && p !== "cls"));
755
+ const isIdentityForward = (matchText) => {
756
+ const paramsMatch = matchText.match(/\(([^)]*)\)/);
757
+ const innerMatch = matchText.match(/(?:return\s+\w+|=>\s*\w+)\s*\(([^)]*)\)/);
758
+ if (!paramsMatch || !innerMatch) return false;
759
+ const params = paramNames(paramsMatch[1]);
760
+ const args = innerMatch[1].split(",").map((a) => a.trim()).filter((a) => a.length > 0);
761
+ if (args.length === 0) return false;
762
+ return args.every((a) => /^[A-Za-z_$][\w$]*$/.test(a) && params.has(a));
549
763
  };
550
764
  const isUseContextWrapper = (matchText) => /\buse\w+/.test(matchText) && /useContext\s*\(/.test(matchText);
551
765
  const detectThinWrappers = (content, relativePath, ext) => {
@@ -565,7 +779,7 @@ const detectThinWrappers = (content, relativePath, ext) => {
565
779
  const prevLine = lines[lineNumber - 2]?.trim();
566
780
  if (prevLine && prevLine.startsWith("@")) continue;
567
781
  }
568
- if (hasHardcodedArgs(matchText)) continue;
782
+ if (!isIdentityForward(matchText)) continue;
569
783
  if (isUseContextWrapper(matchText)) continue;
570
784
  diagnostics.push({
571
785
  filePath: relativePath,
@@ -619,8 +833,9 @@ const detectOverAbstraction = async (context) => {
619
833
  }
620
834
  const relativePath = path.relative(context.rootDirectory, filePath);
621
835
  const ext = path.extname(filePath);
622
- diagnostics.push(...detectThinWrappers(content, relativePath, ext));
623
- diagnostics.push(...detectAiNaming(content, relativePath));
836
+ const codeOnly = maskComments(content, ext);
837
+ diagnostics.push(...detectThinWrappers(codeOnly, relativePath, ext));
838
+ diagnostics.push(...detectAiNaming(codeOnly, relativePath));
624
839
  }
625
840
  return diagnostics;
626
841
  };
@@ -650,8 +865,7 @@ const JUSTIFICATION_OPENERS = [
650
865
  /^(?:First|Then|Finally|Next|Lastly|Subsequently),?\s+(?:it|we|the\s+(?:function|method|class))\b/i
651
866
  ];
652
867
  const EXPLANATORY_OPENERS = /^(Matches|Detects|Represents|Holds|Stores|Tracks|Handles|Manages|Controls|Contains|Captures|Encapsulates|Wraps|Describes)\s+[A-Za-z`'"]/;
653
- const STEP_COMMENT_VERB_RE = /^(?:Render|Enable|Disable|Initialize|Init|Setup|Set|Get|Fetch|Load|Save|Build|Create|Delete|Remove|Add|Update|Process|Execute|Run|Start|Stop|Clean|Cleanup|Configure|Validate|Check|Verify|Parse|Extract|Apply|Wait|Sleep|Skip|Allow|Deny|Lock|Unlock|Refresh|Reload|Reset|Clear|Send|Receive|Read|Write|Print|Log|Emit|Dispatch|Fire|Open|Close|Bind|Connect|Disconnect|Register|Unregister|Push|Pop|Insert|Append|Prepend|Sort|Filter|Find|Search|Replace|Encode|Decode|Convert|Transform|Map|Reduce|Iterate|Loop|Walk|Visit|Mark|Unmark|Toggle|Switch|Restart|Resume|Pause|Abort|Cancel|Compute|Calculate|Resolve|Reject|Ignore|Handle|Track|Trace|Increment|Decrement|Round|Truncate|Resize|Move|Copy|Clone|Merge|Split|Join|Wrap|Unwrap|Bump|Drain|Flush|Sync|Persist|Commit|Rollback|Yield|Return|Discard|Defer|Pin|Unpin|Mount|Unmount|Spawn|Kill|Restore)(?:\s|$)/;
654
- const EXPLANATORY_WHY_MARKERS = /\b(?:because|since|otherwise|workaround|caveat|warning|important|assumes?|note:|bug|issue|see\s+(?:issue|above|below)|in\s+prod|in\s+production|breaks?\s+when|fails?\s+when|must\s+run|must\s+be|has\s+to\s+be|hack\s+for|fix\s+for|reason:|to\s+avoid|to\s+ensure|to\s+prevent|in\s+order\s+to|necessary|guarantee[sd]?|prevents?|regardless\s+of|required\s+(?:for|to|by)|for\s+example|e\.g\.|i\.e\.|useful\s+(?:for|when)|intended\s+to|on\s+purpose|by\s+design)\b/i;
868
+ const EXPLANATORY_WHY_MARKERS = /\b(?:because|since|otherwise|workaround|caveat|warning|important|assumes?|note:|bug|issue|see\s+(?:issue|above|below)|in\s+prod|in\s+production|breaks?\s+when|fails?\s+when|must\s+run|must\s+be|has\s+to\s+be|hack\s+for|fix\s+for|reason:|to\s+avoid|to\s+ensure|to\s+prevent|in\s+order\s+to|necessary|guarantee[sd]?|prevents?|regardless\s+of|required\s+(?:for|to|by)|for\s+example|e\.g\.|i\.e\.|useful\s+(?:for|when)|intended\s+to|on\s+purpose|by\s+design|ideally|however|although|even\s+though|despite|whereas|unfortunately|trade-?off|first\s+need)\b/i;
655
869
  const MEANINGFUL_JSDOC_TAGS = new Set([
656
870
  "deprecated",
657
871
  "see",
@@ -747,7 +961,7 @@ const PHP_DECL_START = /^\s*(?:(?:public|private|protected|static|final|abstract
747
961
 
748
962
  //#endregion
749
963
  //#region src/engines/ai-slop/non-production-paths.ts
750
- const DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3|cli|cli-[\w-]+|[\w-]+-cli)\//i;
964
+ const DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|docs?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3|cli|cli-[\w-]+|[\w-]+-cli)\//i;
751
965
  const BASENAME_PATTERN = /(?:^|\/)(?:benchmark|bench|demo|example|script|seed|migrate|profile|smoke|stress|load|debug|repro)[-_.][^/]*\.[mc]?[jt]sx?$|(?:^|\/)[^/]+[-_](?:benchmark|bench|demo|example)\.[mc]?[jt]sx?$/i;
752
966
  const isNonProductionPath = (relativePath) => DIR_PATTERN.test(relativePath) || BASENAME_PATTERN.test(relativePath);
753
967
 
@@ -756,7 +970,7 @@ const isNonProductionPath = (relativePath) => DIR_PATTERN.test(relativePath) ||
756
970
  const TRIVIAL_VERB_STEMS = "Import|Defin|Initializ|Setting|Set\\s+up|Setup|Return|Check|Loop|Iterat|Creat|Updat|Delet|Remov|Handl|Get|Fetch|Increment|Decrement|Writ|Runn|Run|Pars|Execut|Extract|Sav|Load|Build|Start|Stopp|Stop|Clean(?:up|\\s+up)?|Configur|Validat|Process|Queue|Fire|Emit|Dispatch|Log|Print|Render";
757
971
  const TRIVIAL_JS_COMMENT_PATTERNS = [/\/\/\s*This (?:function|method|class|variable|constant) (?:will |is used to |is responsible for )?/i, new RegExp(`\\/\\/\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
758
972
  const TRIVIAL_PYTHON_COMMENT_PATTERNS = [/^#\s*This (?:function|method|class) (?:will |is used to )?/i, new RegExp(`^#\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
759
- const EXPLANATORY_KEYWORDS = /\b(?:because|since|note|todo|fixme|hack|warn|warning|workaround|caveat|important|assumes?)\b/i;
973
+ const EXPLANATORY_KEYWORDS = /\b(?:because|since|note|todo|fixme|hack|warn|warning|workaround|caveat|important|assumes?|if|when|unless|until|only|except|otherwise|needs?|must|should|ensure|avoid|prevent|requires?)\b/i;
760
974
  const COMMENTED_CODE_CHARS = /[({=;}\]>]/;
761
975
  const MAX_TRIVIAL_COMMENT_LENGTH = 60;
762
976
  const isJsComment = (trimmed) => trimmed.startsWith("//") && !trimmed.startsWith("///") && !trimmed.startsWith("//!");
@@ -905,6 +1119,7 @@ const TODO_PATTERN = new RegExp(`\\b(?:${[
905
1119
  "PLACEHOLDER",
906
1120
  "STUB"
907
1121
  ].join("|")})[:\\s]`);
1122
+ const TODO_TRACKING_RE = /https?:\/\/|#\d+|\bgh-\d+\b|\b[A-Z][A-Z0-9]+-\d+\b|\b(?:issue|ticket|jira)\b/i;
908
1123
  const isBlockCloserAfterReturn = (line) => line.startsWith("}") || line.startsWith("};") || line.startsWith("),") || line.startsWith(");") || line.startsWith("],") || line.startsWith("]);");
909
1124
  const isGuardedSingleLineExit = (lines, lineIndex) => {
910
1125
  const contextLines = [];
@@ -924,7 +1139,10 @@ const detectTodoStubs = (content, relativePath) => {
924
1139
  for (let i = 0; i < lines.length; i++) {
925
1140
  const trimmed = lines[i].trim();
926
1141
  if (!trimmed.startsWith("//") && !trimmed.startsWith("#") && !trimmed.startsWith("*") && !trimmed.startsWith("/*")) continue;
927
- if (TODO_PATTERN.test(trimmed)) diagnostics.push(slop(relativePath, i + 1, "ai-slop/todo-stub", "info", "Unresolved TODO/FIXME/HACK comment indicates incomplete code", "Resolve the TODO or create a tracked issue for it", false));
1142
+ if (TODO_PATTERN.test(trimmed)) {
1143
+ if (TODO_TRACKING_RE.test(trimmed)) continue;
1144
+ diagnostics.push(slop(relativePath, i + 1, "ai-slop/todo-stub", "info", "Unresolved TODO/FIXME/HACK comment indicates incomplete code", "Resolve the TODO or create a tracked issue for it", false));
1145
+ }
928
1146
  }
929
1147
  return diagnostics;
930
1148
  };
@@ -973,9 +1191,10 @@ const detectDeadPatterns = async (context) => {
973
1191
  }
974
1192
  const ext = path.extname(filePath);
975
1193
  const relativePath = path.relative(context.rootDirectory, filePath);
976
- diagnostics.push(...detectConsoleLeftovers(content, relativePath, ext));
1194
+ const codeOnly = maskComments(content, ext);
1195
+ diagnostics.push(...detectConsoleLeftovers(codeOnly, relativePath, ext));
977
1196
  diagnostics.push(...detectTodoStubs(content, relativePath));
978
- diagnostics.push(...detectDeadCodePatterns(content, relativePath, ext));
1197
+ diagnostics.push(...detectDeadCodePatterns(codeOnly, relativePath, ext));
979
1198
  diagnostics.push(...detectUnsafeTypePatterns(content, relativePath, ext));
980
1199
  }
981
1200
  return diagnostics;
@@ -1165,6 +1384,7 @@ const JS_EXTENSIONS$2 = new Set([
1165
1384
  const IMPORT_FROM_RE = /^\s*import\s+([^;]*?)\s+from\s+["']([^"']+)["']/;
1166
1385
  const TYPE_ONLY_RE = /^\s*type\b/;
1167
1386
  const VALUE_BINDING_RE = /\{([^}]*)\}/;
1387
+ const NAMESPACE_RE = /\*\s+as\s+/;
1168
1388
  const isTypeOnly = (clause) => {
1169
1389
  if (TYPE_ONLY_RE.test(clause)) return true;
1170
1390
  const braces = VALUE_BINDING_RE.exec(clause);
@@ -1182,7 +1402,8 @@ const extractImportLines = (content) => {
1182
1402
  results.push({
1183
1403
  spec: match[2],
1184
1404
  line: i + 1,
1185
- typeOnly: isTypeOnly(match[1])
1405
+ typeOnly: isTypeOnly(match[1]),
1406
+ namespace: NAMESPACE_RE.test(match[1])
1186
1407
  });
1187
1408
  }
1188
1409
  return results;
@@ -1199,11 +1420,11 @@ const detectDuplicateImports = async (context) => {
1199
1420
  } catch {
1200
1421
  continue;
1201
1422
  }
1202
- const imports = extractImportLines(content);
1423
+ const imports = extractImportLines(maskComments(content, path.extname(filePath)));
1203
1424
  if (imports.length < 2) continue;
1204
1425
  const byBucket = /* @__PURE__ */ new Map();
1205
1426
  for (const imp of imports) {
1206
- const key = `${imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
1427
+ const key = `${imp.namespace ? "ns" : imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
1207
1428
  const list = byBucket.get(key) ?? [];
1208
1429
  list.push(imp);
1209
1430
  byBucket.set(key, list);
@@ -1450,6 +1671,30 @@ const LOOPBACK_HOSTS = new Set([
1450
1671
  "0.0.0.0",
1451
1672
  "::1"
1452
1673
  ]);
1674
+ const VENDOR_API_DOMAINS = [
1675
+ "github.com",
1676
+ "githubusercontent.com",
1677
+ "googleapis.com",
1678
+ "accounts.google.com",
1679
+ "stripe.com",
1680
+ "openai.com",
1681
+ "anthropic.com",
1682
+ "slack.com",
1683
+ "twilio.com",
1684
+ "sendgrid.com",
1685
+ "mailgun.net",
1686
+ "cloudflare.com",
1687
+ "discord.com",
1688
+ "telegram.org",
1689
+ "login.microsoftonline.com",
1690
+ "graph.microsoft.com",
1691
+ "twitter.com",
1692
+ "x.com",
1693
+ "twimg.com",
1694
+ "t.co",
1695
+ "api.telegram.org"
1696
+ ];
1697
+ const isVendorApiHost = (host) => VENDOR_API_DOMAINS.some((d) => host === d || host.endsWith(`.${d}`));
1453
1698
  const PLACEHOLDER_ID_RE = /^(?:changeme|replace[_-]?me|your[_-]|example|placeholder|todo)/i;
1454
1699
  const HARDCODED_URL_FINDING = {
1455
1700
  rule: "ai-slop/hardcoded-url",
@@ -1494,6 +1739,7 @@ const shouldFlagUrlLiteral = (line, urlText) => {
1494
1739
  if (!host) return false;
1495
1740
  if (PLACEHOLDER_HOSTS.has(host)) return false;
1496
1741
  if (LOOPBACK_HOSTS.has(host)) return false;
1742
+ if (isVendorApiHost(host)) return false;
1497
1743
  if (DOC_URL_CONTEXT_RE.test(line) && !ENVIRONMENT_HOST_RE.test(host)) return false;
1498
1744
  return URL_CONFIG_CONTEXT_RE.test(line) || ENVIRONMENT_HOST_RE.test(host);
1499
1745
  };
@@ -1545,7 +1791,7 @@ const detectHardcodedConfigLiterals = async (context) => {
1545
1791
  }
1546
1792
  const relativePath = path.relative(context.rootDirectory, filePath);
1547
1793
  const ext = path.extname(filePath);
1548
- diagnostics.push(...scanFileForConfigLiterals(content, relativePath, ext));
1794
+ diagnostics.push(...scanFileForConfigLiterals(maskComments(content, ext), relativePath, ext));
1549
1795
  }
1550
1796
  return diagnostics;
1551
1797
  };
@@ -1678,7 +1924,9 @@ const PYTHON_STDLIB = new Set([
1678
1924
  "builtins",
1679
1925
  "bz2",
1680
1926
  "calendar",
1927
+ "code",
1681
1928
  "codecs",
1929
+ "codeop",
1682
1930
  "collections",
1683
1931
  "concurrent",
1684
1932
  "configparser",
@@ -1751,6 +1999,7 @@ const PYTHON_STDLIB = new Set([
1751
1999
  "readline",
1752
2000
  "reprlib",
1753
2001
  "resource",
2002
+ "rlcompleter",
1754
2003
  "secrets",
1755
2004
  "select",
1756
2005
  "selectors",
@@ -1939,6 +2188,8 @@ const collectFromPyproject = (rootDir, pyDeps) => {
1939
2188
  }
1940
2189
  const extras = content.match(/\[project\.optional-dependencies\]([\s\S]*?)(?=\n\[|$)/);
1941
2190
  if (extras) for (const m of extras[1].matchAll(/["']\s*([a-zA-Z][a-zA-Z0-9_\-.]+)/g)) addPyDep(pyDeps, m[1]);
2191
+ const groups = content.match(/\[dependency-groups\]([\s\S]*?)(?=\n\[[^[]|$)/);
2192
+ if (groups) for (const m of groups[1].matchAll(/["']\s*([a-zA-Z][a-zA-Z0-9_\-.]+)/g)) addPyDep(pyDeps, m[1]);
1942
2193
  const poetryRe = /\[tool\.poetry(?:\.group\.[a-z]+)?\.dependencies\]([\s\S]*?)(?=\n\[|$)/g;
1943
2194
  let match = poetryRe.exec(content);
1944
2195
  while (match !== null) {
@@ -2171,9 +2422,28 @@ const extractJsImports = (content) => {
2171
2422
  const extractPyImports = (content) => {
2172
2423
  const lines = content.split("\n");
2173
2424
  const results = [];
2425
+ let inDoc = null;
2426
+ let typeCheckIndent = -1;
2174
2427
  for (let i = 0; i < lines.length; i++) {
2175
- const line = lines[i].trim();
2176
- if (line.startsWith("#")) continue;
2428
+ const raw = lines[i];
2429
+ const line = raw.trim();
2430
+ if (inDoc) {
2431
+ if (line.includes(inDoc)) inDoc = null;
2432
+ continue;
2433
+ }
2434
+ if (line === "" || line.startsWith("#")) continue;
2435
+ const triples = line.match(/"""|'''/g);
2436
+ if (triples) {
2437
+ if (triples.length % 2 === 1) inDoc = triples[triples.length - 1];
2438
+ continue;
2439
+ }
2440
+ const indent = raw.length - raw.trimStart().length;
2441
+ if (typeCheckIndent >= 0 && indent <= typeCheckIndent) typeCheckIndent = -1;
2442
+ if (/^if\s+(?:[\w.]+\.)?TYPE_CHECKING\b/.test(line)) {
2443
+ typeCheckIndent = indent;
2444
+ continue;
2445
+ }
2446
+ if (typeCheckIndent >= 0) continue;
2177
2447
  const fromMatch = line.match(/^from\s+([\w.]+)\s+import\b/);
2178
2448
  if (fromMatch && !fromMatch[1].startsWith(".")) {
2179
2449
  results.push({
@@ -2183,8 +2453,8 @@ const extractPyImports = (content) => {
2183
2453
  continue;
2184
2454
  }
2185
2455
  const importMatch = line.match(/^import\s+([\w.,\s]+?)(?:\s+as\s+\w+)?\s*$/);
2186
- if (importMatch) for (const raw of importMatch[1].split(",")) {
2187
- const cleaned = raw.trim().split(/\s+as\s+/)[0];
2456
+ if (importMatch) for (const part of importMatch[1].split(",")) {
2457
+ const cleaned = part.trim().split(/\s+as\s+/)[0];
2188
2458
  if (cleaned && !cleaned.startsWith(".")) results.push({
2189
2459
  spec: cleaned,
2190
2460
  line: i + 1
@@ -2241,6 +2511,7 @@ const detectHallucinatedImports = async (context) => {
2241
2511
  continue;
2242
2512
  }
2243
2513
  const relPath = path.relative(context.rootDirectory, filePath);
2514
+ if (isNonProductionPath(relPath)) continue;
2244
2515
  const imports = isJs ? extractJsImports(content) : extractPyImports(content);
2245
2516
  for (const { spec, line } of imports) {
2246
2517
  const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
@@ -2368,7 +2639,7 @@ const collectBlocks = (sourceLines, syntax) => {
2368
2639
  //#endregion
2369
2640
  //#region src/engines/ai-slop/meta-comment.ts
2370
2641
  const PLAN_REFERENCE_RES = [
2371
- /\b(?:stage|step|phase)\s+\d+\b(?!\s*[:.]?\s*(?:bytes|ms|seconds|of\s+\d))/i,
2642
+ /^(?:stage|step|phase)\s+\d+\s*[:.\-–—]/i,
2372
2643
  /\bstep\s+\d+\s+of\s+the\s+plan\b/i,
2373
2644
  /\bas\s+(?:per|requested)\s+(?:the\s+)?(?:requirements?|spec|task|ticket|prompt|instructions?)\b/i,
2374
2645
  /\bper\s+the\s+(?:spec|requirements?|task|ticket|plan|prompt|instructions?)\b/i,
@@ -2470,24 +2741,6 @@ const looksLikeLicenseHeader = (block) => {
2470
2741
  const text = block.rawLines.join(" ").toLowerCase();
2471
2742
  return text.includes("copyright") || text.includes("license") || text.includes("spdx-license-identifier");
2472
2743
  };
2473
- const BARE_LABEL_RE = /^[A-Z][A-Za-z0-9 ]{1,28}$/;
2474
- const isBareSectionLabel = (prose) => {
2475
- if (!BARE_LABEL_RE.test(prose)) return false;
2476
- if (prose.endsWith(".")) return false;
2477
- if (prose.split(/\s+/).length > 3) return false;
2478
- if (STEP_COMMENT_VERB_RE.test(prose)) return false;
2479
- return true;
2480
- };
2481
- const DATA_ENTRY_START = /^\s*(?:\{|\[|["'`]|\d|\w+:\s|case\s)/;
2482
- const nextLineLooksLikeDataEntry = (nextLine) => {
2483
- if (nextLine === null) return false;
2484
- if (!DATA_ENTRY_START.test(nextLine)) return false;
2485
- const trimmed = nextLine.trim();
2486
- if (trimmed.startsWith("case ")) return true;
2487
- if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("\"") || trimmed.startsWith("'") || trimmed.startsWith("`")) return true;
2488
- if (/^\w+\s*:/.test(trimmed)) return true;
2489
- return false;
2490
- };
2491
2744
  const looksLikeSuppressDirective = (block) => block.rawLines.some((l) => /\b(biome-ignore|eslint-disable|ts-ignore|ts-expect-error|@ts-\w+|noqa|pylint:\s*disable|rubocop:disable|noinspection|phpcs:disable)\b/.test(l));
2492
2745
  const GO_DECL_NAME_RE = /^(?:func|type|var|const)\s+(?:\([^)]*\)\s*)?(\w+)/;
2493
2746
  const GO_FIELD_LEAD_RE = /^(\w+)\s+/;
@@ -2576,10 +2829,6 @@ const detectNarrativeInBlock = (block, ext) => {
2576
2829
  matched: true,
2577
2830
  reason: "phase/section header"
2578
2831
  };
2579
- if (block.kind === "line" && block.prose.length === 1 && isBareSectionLabel(block.prose[0]) && !nextLineLooksLikeDataEntry(block.nextNonBlankLine) && !looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
2580
- matched: true,
2581
- reason: "bare section label"
2582
- };
2583
2832
  const joined = block.prose.join(" ");
2584
2833
  const hasWhyMarker = EXPLANATORY_WHY_MARKERS.test(joined);
2585
2834
  if (hasWhyMarker || hasDocIndicator(block)) return {
@@ -2610,17 +2859,11 @@ const detectNarrativeInBlock = (block, ext) => {
2610
2859
  };
2611
2860
  const nonEmptyProseCount = block.prose.filter((l) => l.length > 0).length;
2612
2861
  const isAboveDeclaration = looksLikeDeclarationPreamble(block.nextNonBlankLine, ext);
2613
- if (nonEmptyProseCount >= 5) {
2614
- if (isAboveDeclaration) return {
2615
- matched: false,
2616
- reason: ""
2617
- };
2618
- return {
2619
- matched: true,
2620
- reason: "long narrative block"
2621
- };
2622
- }
2623
- if (nonEmptyProseCount >= 3 && !hasWhyMarker && block.kind === "line" && !isAboveDeclaration) return {
2862
+ if (nonEmptyProseCount >= 5 && !isAboveDeclaration && hasPreambleSlopSignal(block)) return {
2863
+ matched: true,
2864
+ reason: "long narrative block"
2865
+ };
2866
+ if (nonEmptyProseCount >= 3 && !hasWhyMarker && block.kind === "line" && !isAboveDeclaration && hasPreambleSlopSignal(block)) return {
2624
2867
  matched: true,
2625
2868
  reason: "multi-line narrative prose"
2626
2869
  };
@@ -3054,7 +3297,7 @@ const detectRustPatterns = async (context) => {
3054
3297
 
3055
3298
  //#endregion
3056
3299
  //#region src/engines/ai-slop/silent-recovery.ts
3057
- const JS_EXTS$1 = new Set([
3300
+ const JS_EXTS = new Set([
3058
3301
  ".ts",
3059
3302
  ".tsx",
3060
3303
  ".js",
@@ -3062,7 +3305,14 @@ const JS_EXTS$1 = new Set([
3062
3305
  ".mjs",
3063
3306
  ".cjs"
3064
3307
  ]);
3065
- const CATCH_HEAD_RE = /\bcatch\s*(?:\([^)]*\))?\s*\{/g;
3308
+ const CATCH_HEAD_RE = /\bcatch\s*(?:\(\s*([^)]*?)\s*\))?\s*\{/g;
3309
+ const isIdentifier = (s) => /^[A-Za-z_$][\w$]*$/.test(s);
3310
+ const recoveryDropsError = (binding, body) => {
3311
+ const name = binding?.trim() ?? "";
3312
+ if (name === "") return true;
3313
+ if (!isIdentifier(name)) return false;
3314
+ return !new RegExp(`\\b${name}\\b`).test(body);
3315
+ };
3066
3316
  const LOG_STATEMENT_RE = /^(?:console|[\w$]+(?:\.[\w$]+)*)\.(?:log|info|warn|warning|error|debug|trace)\s*\(/;
3067
3317
  const HANDLING_TOKEN_RE = /\b(?:throw|return|reject|next|process\.exit|continue|break)\b/;
3068
3318
  const stripBlockComments = (text) => text.replace(/\/\*[\s\S]*?\*\//g, "");
@@ -3112,14 +3362,15 @@ const detectJsSilentRecovery = (content, relPath) => {
3112
3362
  const body = extractCatchBody(content, match.index + match[0].length - 1);
3113
3363
  if (body === null) continue;
3114
3364
  if (!isLogOnlyBody(body)) continue;
3365
+ if (!recoveryDropsError(match[1], body)) continue;
3115
3366
  const line = content.slice(0, match.index).split("\n").length;
3116
3367
  out.push({
3117
3368
  filePath: relPath,
3118
3369
  engine: "ai-slop",
3119
3370
  rule: "ai-slop/silent-recovery",
3120
3371
  severity: "warning",
3121
- message: "Catch only logs then continues, leaving execution in a possibly broken state",
3122
- help: "Handle the error: rethrow, return an error value, or recover explicitly. Logging alone lets the program proceed as if nothing failed.",
3372
+ message: "Catch logs without the caught error then continues; the failure cause is lost",
3373
+ help: "Include the caught error in the log, or rethrow / recover explicitly, so the failure stays diagnosable.",
3123
3374
  line,
3124
3375
  column: 0,
3125
3376
  category: "AI Slop",
@@ -3129,6 +3380,7 @@ const detectJsSilentRecovery = (content, relPath) => {
3129
3380
  return out;
3130
3381
  };
3131
3382
  const PY_EXCEPT_RE = /^(\s*)except\b[^\n]*:\s*(?:#.*)?$/;
3383
+ const PY_EXCEPT_BINDING_RE = /\bas\s+(\w+)\s*:/;
3132
3384
  const PY_LOG_STATEMENT_RE = /^(?:logging|logger|log|self\.log|self\.logger|print)(?:\.(?:debug|info|warning|warn|error|exception|critical))?\s*\(/;
3133
3385
  const PY_HANDLING_TOKEN_RE = /^(?:raise\b|return\b|continue\b|break\b|self\.|[\w.]+\s*=)/;
3134
3386
  const detectPySilentRecovery = (content, relPath) => {
@@ -3152,13 +3404,14 @@ const detectPySilentRecovery = (content, relPath) => {
3152
3404
  const allLogs = bodyLines.every((line) => PY_LOG_STATEMENT_RE.test(line) || /^[\w"'(),.\s+:%{}[\]-]+$/.test(line));
3153
3405
  const sawLog = bodyLines.some((line) => PY_LOG_STATEMENT_RE.test(line));
3154
3406
  if (!allLogs || !sawLog) continue;
3407
+ if (!recoveryDropsError(PY_EXCEPT_BINDING_RE.exec(lines[i])?.[1], bodyLines.join(" "))) continue;
3155
3408
  out.push({
3156
3409
  filePath: relPath,
3157
3410
  engine: "ai-slop",
3158
3411
  rule: "ai-slop/silent-recovery",
3159
3412
  severity: "warning",
3160
- message: "except only logs then continues, leaving execution in a possibly broken state",
3161
- help: "Handle the error: re-raise, return an error value, or recover explicitly. Logging alone lets the program proceed as if nothing failed.",
3413
+ message: "except logs without the caught error then continues; the failure cause is lost",
3414
+ help: "Include the caught error in the log, or re-raise / recover explicitly, so the failure stays diagnosable.",
3162
3415
  line: i + 1,
3163
3416
  column: 0,
3164
3417
  category: "AI Slop",
@@ -3173,7 +3426,7 @@ const detectSilentRecovery = async (context) => {
3173
3426
  for (const filePath of files) {
3174
3427
  if (isAutoGenerated(filePath)) continue;
3175
3428
  const ext = path.extname(filePath);
3176
- const isJs = JS_EXTS$1.has(ext);
3429
+ const isJs = JS_EXTS.has(ext);
3177
3430
  if (!isJs && !(ext === ".py")) continue;
3178
3431
  const relPath = path.relative(context.rootDirectory, filePath);
3179
3432
  if (isNonProductionPath(relPath)) continue;
@@ -3260,10 +3513,11 @@ const extractPyImportedSymbols = (lines) => {
3260
3513
  const importLines = /* @__PURE__ */ new Set();
3261
3514
  for (let i = 0; i < lines.length; i++) {
3262
3515
  const trimmed = lines[i].trim();
3263
- const fromMatch = trimmed.match(/^from\s+[\w.]+\s+import\s+(.+)/);
3516
+ const fromMatch = trimmed.match(/^from\s+([\w.]+)\s+import\s+(.+)/);
3264
3517
  if (fromMatch) {
3265
3518
  importLines.add(i);
3266
- const importPart = fromMatch[1].replace(/#.*$/, "").trim();
3519
+ if (fromMatch[1] === "__future__") continue;
3520
+ const importPart = fromMatch[2].replace(/#.*$/, "").trim();
3267
3521
  if (importPart === "*") continue;
3268
3522
  const cleaned = importPart.replace(/[()]/g, "");
3269
3523
  for (const item of cleaned.split(",")) {
@@ -3651,12 +3905,92 @@ const findBraceFunctionEnd = (lines, startIndex) => {
3651
3905
  maxNesting
3652
3906
  };
3653
3907
  };
3654
- const findPythonFunctionEnd = (lines, startIndex) => {
3655
- const baseIndent = lines[startIndex].match(/^(\s*)/)?.[1].length ?? 0;
3656
- let endLine = startIndex;
3908
+ const extractPythonSignature = (lines, startIndex) => {
3909
+ let depth = 0;
3910
+ let started = false;
3911
+ let params = "";
3912
+ for (let j = startIndex; j < lines.length; j++) {
3913
+ const l = lines[j];
3914
+ for (let ci = 0; ci < l.length; ci++) {
3915
+ const ch = l[ci];
3916
+ if (ch === "(") {
3917
+ depth++;
3918
+ if (depth === 1 && !started) {
3919
+ started = true;
3920
+ continue;
3921
+ }
3922
+ } else if (ch === ")") {
3923
+ depth--;
3924
+ if (depth === 0) return {
3925
+ params,
3926
+ sigEndIndex: j
3927
+ };
3928
+ }
3929
+ if (started) params += ch;
3930
+ }
3931
+ if (started) params += " ";
3932
+ }
3933
+ return {
3934
+ params,
3935
+ sigEndIndex: startIndex
3936
+ };
3937
+ };
3938
+ const countPythonParams = (signature) => {
3939
+ let depth = 0;
3940
+ const parts = [];
3941
+ let current = "";
3942
+ for (const ch of signature) {
3943
+ if (ch === "(" || ch === "[" || ch === "{") depth++;
3944
+ else if (ch === ")" || ch === "]" || ch === "}") depth--;
3945
+ if (ch === "," && depth === 0) {
3946
+ parts.push(current);
3947
+ current = "";
3948
+ continue;
3949
+ }
3950
+ current += ch;
3951
+ }
3952
+ parts.push(current);
3953
+ let count = 0;
3954
+ for (const raw of parts) {
3955
+ const p = raw.trim();
3956
+ if (p.length === 0 || p === "*" || p === "/") continue;
3957
+ if (p.startsWith("*")) continue;
3958
+ if (p.includes("=")) continue;
3959
+ const name = p.split(":")[0].trim();
3960
+ if (name === "self" || name === "cls") continue;
3961
+ count++;
3962
+ }
3963
+ return count;
3964
+ };
3965
+ const countPythonBodyCodeLines = (lines, sigEndIndex, endLine) => {
3966
+ let count = 0;
3967
+ let inDoc = false;
3968
+ let delim = "";
3969
+ for (let j = sigEndIndex + 1; j <= endLine && j < lines.length; j++) {
3970
+ const t = lines[j].trim();
3971
+ if (inDoc) {
3972
+ if (t.includes(delim)) inDoc = false;
3973
+ continue;
3974
+ }
3975
+ if (t === "" || t.startsWith("#")) continue;
3976
+ const opener = t.startsWith("\"\"\"") ? "\"\"\"" : t.startsWith("'''") ? "'''" : "";
3977
+ if (opener) {
3978
+ if (!t.slice(3).includes(opener)) {
3979
+ inDoc = true;
3980
+ delim = opener;
3981
+ }
3982
+ continue;
3983
+ }
3984
+ count++;
3985
+ }
3986
+ return count;
3987
+ };
3988
+ const findPythonFunctionEnd = (lines, defIndex, bodyStartIndex) => {
3989
+ const baseIndent = lines[defIndex].match(/^(\s*)/)?.[1].length ?? 0;
3990
+ let endLine = bodyStartIndex;
3657
3991
  let maxNesting = 0;
3658
3992
  const controlIndentStack = [];
3659
- for (let j = startIndex + 1; j < lines.length; j++) {
3993
+ for (let j = bodyStartIndex + 1; j < lines.length; j++) {
3660
3994
  const l = lines[j];
3661
3995
  if (l.trim() === "") {
3662
3996
  endLine = j;
@@ -3678,7 +4012,10 @@ const findPythonFunctionEnd = (lines, startIndex) => {
3678
4012
  };
3679
4013
  };
3680
4014
  const findFunctionEnd = (lines, startIndex, isPython) => {
3681
- if (isPython) return findPythonFunctionEnd(lines, startIndex);
4015
+ if (isPython) {
4016
+ const { sigEndIndex } = extractPythonSignature(lines, startIndex);
4017
+ return findPythonFunctionEnd(lines, startIndex, sigEndIndex);
4018
+ }
3682
4019
  return findBraceFunctionEnd(lines, startIndex);
3683
4020
  };
3684
4021
  const isBlockArrow = (lines, startIndex) => {
@@ -3743,7 +4080,7 @@ const FUNCTION_PATTERNS = [
3743
4080
  ]
3744
4081
  },
3745
4082
  {
3746
- regex: /^\s*def\s+(\w+)\s*\(([^)]*)\)/,
4083
+ regex: /^\s*(?:async\s+)?def\s+(\w+)\s*\(/,
3747
4084
  langFilter: [".py"]
3748
4085
  },
3749
4086
  {
@@ -3800,14 +4137,23 @@ const analyzeFunctions = (content, ext) => {
3800
4137
  const isPython = fnMatch.patternIndex === 2;
3801
4138
  if (fnMatch.patternIndex === 1 && !isBlockArrow(lines, i)) continue;
3802
4139
  const { endLine, maxNesting } = findFunctionEnd(lines, i, isPython);
3803
- const bodyLines = lines.slice(i + 1, endLine);
3804
- const templateLines = isPython ? 0 : countTemplateLines(bodyLines);
4140
+ let templateLines;
4141
+ let paramCount;
4142
+ if (isPython) {
4143
+ const sig = extractPythonSignature(lines, i);
4144
+ const codeLines = countPythonBodyCodeLines(lines, sig.sigEndIndex, endLine);
4145
+ templateLines = endLine - i + 1 - codeLines;
4146
+ paramCount = countPythonParams(sig.params);
4147
+ } else {
4148
+ templateLines = countTemplateLines(lines.slice(i + 1, endLine));
4149
+ paramCount = countParams(fnMatch.params);
4150
+ }
3805
4151
  functions.push({
3806
4152
  name: fnMatch.name,
3807
4153
  startLine: i + 1,
3808
4154
  lineCount: endLine - i + 1,
3809
4155
  maxNesting,
3810
- paramCount: countParams(fnMatch.params),
4156
+ paramCount,
3811
4157
  templateLines
3812
4158
  });
3813
4159
  }
@@ -5519,212 +5865,6 @@ const runCargoAudit = async (rootDir, timeout) => {
5519
5865
  }
5520
5866
  };
5521
5867
 
5522
- //#endregion
5523
- //#region src/utils/source-masker.ts
5524
- const JS_EXTS = new Set([
5525
- ".ts",
5526
- ".tsx",
5527
- ".js",
5528
- ".jsx",
5529
- ".mjs",
5530
- ".cjs"
5531
- ]);
5532
- const PY_EXTS = new Set([".py"]);
5533
- const RB_EXTS = new Set([".rb"]);
5534
- const PHP_EXTS = new Set([".php"]);
5535
- const familyForExt = (ext) => {
5536
- if (JS_EXTS.has(ext)) return "js";
5537
- if (PY_EXTS.has(ext)) return "py";
5538
- if (RB_EXTS.has(ext)) return "rb";
5539
- if (PHP_EXTS.has(ext)) return "php";
5540
- return "none";
5541
- };
5542
- const maskStringsAndComments = (content, ext) => {
5543
- const family = familyForExt(ext);
5544
- if (family === "none") return content;
5545
- if (family === "js") return maskJs(content);
5546
- return maskSimple(content, family);
5547
- };
5548
- const handleQuotesAndComments = (content, i, tplStack, mask) => {
5549
- const len = content.length;
5550
- const c = content[i];
5551
- const next = content[i + 1];
5552
- if (c === "\"" || c === "'") {
5553
- const strStart = i;
5554
- const end = consumeQuotedString(content, i, c);
5555
- mask(strStart + 1, end - 1);
5556
- return {
5557
- handled: true,
5558
- nextI: end
5559
- };
5560
- }
5561
- if (c === "`") {
5562
- const scan = consumeTemplateString(content, i + 1);
5563
- mask(i + 1, scan.maskEnd);
5564
- if (scan.openedInterp) tplStack.push(0);
5565
- return {
5566
- handled: true,
5567
- nextI: scan.resumeAt
5568
- };
5569
- }
5570
- if (c === "/" && next === "/") {
5571
- const strStart = i;
5572
- let k = i;
5573
- while (k < len && content[k] !== "\n") k++;
5574
- mask(strStart, k);
5575
- return {
5576
- handled: true,
5577
- nextI: k
5578
- };
5579
- }
5580
- if (c === "/" && next === "*") {
5581
- const strStart = i;
5582
- let k = i + 2;
5583
- while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
5584
- if (k < len - 1) k += 2;
5585
- mask(strStart, k);
5586
- return {
5587
- handled: true,
5588
- nextI: k
5589
- };
5590
- }
5591
- return {
5592
- handled: false,
5593
- nextI: i
5594
- };
5595
- };
5596
- const maskJs = (content) => {
5597
- const out = content.split("");
5598
- const len = content.length;
5599
- const tplStack = [];
5600
- let i = 0;
5601
- const mask = (start, end) => {
5602
- for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
5603
- };
5604
- while (i < len) {
5605
- const c = content[i];
5606
- if (tplStack.length > 0) {
5607
- if (c === "{") {
5608
- tplStack[tplStack.length - 1]++;
5609
- i++;
5610
- continue;
5611
- }
5612
- if (c === "}") {
5613
- if (tplStack[tplStack.length - 1] === 0) {
5614
- tplStack.pop();
5615
- const scan = consumeTemplateString(content, i + 1);
5616
- mask(i + 1, scan.maskEnd);
5617
- if (scan.openedInterp) tplStack.push(0);
5618
- i = scan.resumeAt;
5619
- continue;
5620
- }
5621
- tplStack[tplStack.length - 1]--;
5622
- i++;
5623
- continue;
5624
- }
5625
- }
5626
- const handled = handleQuotesAndComments(content, i, tplStack, mask);
5627
- if (handled.handled) {
5628
- i = handled.nextI;
5629
- continue;
5630
- }
5631
- i++;
5632
- }
5633
- return out.join("");
5634
- };
5635
- const consumeQuotedString = (content, start, quote) => {
5636
- const len = content.length;
5637
- let i = start + 1;
5638
- while (i < len) {
5639
- const c = content[i];
5640
- if (c === "\\" && i + 1 < len) {
5641
- i += 2;
5642
- continue;
5643
- }
5644
- if (c === quote) return i + 1;
5645
- if (c === "\n") return i;
5646
- i++;
5647
- }
5648
- return i;
5649
- };
5650
- const consumeTemplateString = (content, start) => {
5651
- const len = content.length;
5652
- let i = start;
5653
- while (i < len) {
5654
- const c = content[i];
5655
- if (c === "\\" && i + 1 < len) {
5656
- i += 2;
5657
- continue;
5658
- }
5659
- if (c === "`") return {
5660
- maskEnd: i,
5661
- resumeAt: i + 1,
5662
- openedInterp: false
5663
- };
5664
- if (c === "$" && content[i + 1] === "{") return {
5665
- maskEnd: i,
5666
- resumeAt: i + 2,
5667
- openedInterp: true
5668
- };
5669
- i++;
5670
- }
5671
- return {
5672
- maskEnd: i,
5673
- resumeAt: i,
5674
- openedInterp: false
5675
- };
5676
- };
5677
- const maskSimple = (content, family) => {
5678
- const out = content.split("");
5679
- const len = content.length;
5680
- let i = 0;
5681
- const mask = (start, end) => {
5682
- for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
5683
- };
5684
- while (i < len) {
5685
- const c = content[i];
5686
- const next = content[i + 1];
5687
- if (family === "py" && (c === "\"" || c === "'")) {
5688
- if (content[i + 1] === c && content[i + 2] === c) {
5689
- const triple = c + c + c;
5690
- const end = content.indexOf(triple, i + 3);
5691
- const stop = end === -1 ? len : end + 3;
5692
- mask(i + 3, stop - 3);
5693
- i = stop;
5694
- continue;
5695
- }
5696
- }
5697
- if (c === "\"" || c === "'") {
5698
- const strStart = i;
5699
- i = consumeQuotedString(content, i, c);
5700
- mask(strStart + 1, i - 1);
5701
- continue;
5702
- }
5703
- if ((family === "py" || family === "rb" || family === "php") && c === "#") {
5704
- const strStart = i;
5705
- while (i < len && content[i] !== "\n") i++;
5706
- mask(strStart, i);
5707
- continue;
5708
- }
5709
- if (family === "php" && c === "/" && next === "/") {
5710
- const strStart = i;
5711
- while (i < len && content[i] !== "\n") i++;
5712
- mask(strStart, i);
5713
- continue;
5714
- }
5715
- if (family === "php" && c === "/" && next === "*") {
5716
- const strStart = i;
5717
- i += 2;
5718
- while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
5719
- if (i < len - 1) i += 2;
5720
- mask(strStart, i);
5721
- continue;
5722
- }
5723
- i++;
5724
- }
5725
- return out.join("");
5726
- };
5727
-
5728
5868
  //#endregion
5729
5869
  //#region src/engines/security/risky.ts
5730
5870
  const ev = "eval";
@@ -5999,6 +6139,7 @@ const scanSecrets = async (context) => {
5999
6139
  } catch {
6000
6140
  continue;
6001
6141
  }
6142
+ content = maskComments(content, path.extname(filePath));
6002
6143
  const relativePath = path.relative(context.rootDirectory, filePath);
6003
6144
  for (const { pattern, name, keywordPrefixed } of SECRET_PATTERNS) {
6004
6145
  const regex = new RegExp(pattern.source, pattern.flags);
@@ -6087,6 +6228,13 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
6087
6228
  //#endregion
6088
6229
  //#region src/scoring/index.ts
6089
6230
  const PERFECT_SCORE = 100;
6231
+ const STYLE_RULES = new Set([
6232
+ "ai-slop/trivial-comment",
6233
+ "ai-slop/narrative-comment",
6234
+ "complexity/file-too-large",
6235
+ "complexity/function-too-long"
6236
+ ]);
6237
+ const STYLE_WEIGHT = .5;
6090
6238
  const getEffectiveFileCount = (diagnostics, sourceFileCount) => {
6091
6239
  if (typeof sourceFileCount === "number" && sourceFileCount > 0) return sourceFileCount;
6092
6240
  const filesWithDiagnostics = new Set(diagnostics.map((d) => d.filePath)).size;
@@ -6101,7 +6249,8 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
6101
6249
  for (const d of diagnostics) {
6102
6250
  const engineWeight = weights[d.engine] ?? 1;
6103
6251
  const severityPenalty = d.severity === "error" ? 3 : d.severity === "warning" ? 1 : .25;
6104
- deductions += severityPenalty * engineWeight;
6252
+ const styleFactor = STYLE_RULES.has(d.rule) ? STYLE_WEIGHT : 1;
6253
+ deductions += severityPenalty * engineWeight * styleFactor;
6105
6254
  }
6106
6255
  const effectiveFileCount = getEffectiveFileCount(diagnostics, sourceFileCount);
6107
6256
  const smoothingConstant = typeof smoothing === "number" ? smoothing : 10;
@@ -6468,7 +6617,7 @@ const handleAislopBaseline = (input) => {
6468
6617
 
6469
6618
  //#endregion
6470
6619
  //#region src/version.ts
6471
- const APP_VERSION = "0.9.6";
6620
+ const APP_VERSION = "0.10.1";
6472
6621
 
6473
6622
  //#endregion
6474
6623
  //#region src/telemetry/env.ts