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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as getEngineLabel, t as ENGINE_INFO } from "./engine-info-DCvIfZ0f.js";
2
2
  import { n as runSubprocess, t as isToolInstalled } from "./subprocess-CQUJDGgn.js";
3
- import { t as APP_VERSION } from "./version-CPpO6jbj.js";
3
+ import { t as APP_VERSION } from "./version-rlhQD8Qh.js";
4
4
  import { r as runGenericLinter, t as fixRubyLint } from "./generic-D_T4cUaC.js";
5
5
  import { n as runExpoDoctor } from "./expo-doctor-BcIkOte5.js";
6
6
  import { createRequire, isBuiltin } from "node:module";
@@ -1254,7 +1254,7 @@ const doctorCommand = async (directory, options = {}) => {
1254
1254
  };
1255
1255
 
1256
1256
  //#endregion
1257
- //#region src/engines/ai-slop/abstractions.ts
1257
+ //#region src/utils/source-masker.ts
1258
1258
  const JS_EXTS$2 = new Set([
1259
1259
  ".ts",
1260
1260
  ".tsx",
@@ -1263,14 +1263,226 @@ const JS_EXTS$2 = new Set([
1263
1263
  ".mjs",
1264
1264
  ".cjs"
1265
1265
  ]);
1266
+ const PY_EXTS = new Set([".py"]);
1267
+ const RB_EXTS = new Set([".rb"]);
1268
+ const PHP_EXTS = new Set([".php"]);
1269
+ const familyForExt = (ext) => {
1270
+ if (JS_EXTS$2.has(ext)) return "js";
1271
+ if (PY_EXTS.has(ext)) return "py";
1272
+ if (RB_EXTS.has(ext)) return "rb";
1273
+ if (PHP_EXTS.has(ext)) return "php";
1274
+ return "none";
1275
+ };
1276
+ const maskStringsAndComments = (content, ext) => {
1277
+ const family = familyForExt(ext);
1278
+ if (family === "none") return content;
1279
+ if (family === "js") return maskJs(content, true);
1280
+ return maskSimple(content, family, true);
1281
+ };
1282
+ const maskComments = (content, ext) => {
1283
+ const family = familyForExt(ext);
1284
+ if (family === "none") return content;
1285
+ if (family === "js") return maskJs(content, false);
1286
+ return maskSimple(content, family, false);
1287
+ };
1288
+ const handleQuotesAndComments = (content, i, tplStack, mask, maskStrings) => {
1289
+ const len = content.length;
1290
+ const c = content[i];
1291
+ const next = content[i + 1];
1292
+ if (c === "\"" || c === "'") {
1293
+ const strStart = i;
1294
+ const end = consumeQuotedString(content, i, c);
1295
+ if (maskStrings) mask(strStart + 1, end - 1);
1296
+ return {
1297
+ handled: true,
1298
+ nextI: end
1299
+ };
1300
+ }
1301
+ if (c === "`") {
1302
+ const scan = consumeTemplateString(content, i + 1);
1303
+ if (maskStrings) mask(i + 1, scan.maskEnd);
1304
+ if (scan.openedInterp) tplStack.push(0);
1305
+ return {
1306
+ handled: true,
1307
+ nextI: scan.resumeAt
1308
+ };
1309
+ }
1310
+ if (c === "/" && next === "/") {
1311
+ const strStart = i;
1312
+ let k = i;
1313
+ while (k < len && content[k] !== "\n") k++;
1314
+ mask(strStart, k);
1315
+ return {
1316
+ handled: true,
1317
+ nextI: k
1318
+ };
1319
+ }
1320
+ if (c === "/" && next === "*") {
1321
+ const strStart = i;
1322
+ let k = i + 2;
1323
+ while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
1324
+ if (k < len - 1) k += 2;
1325
+ mask(strStart, k);
1326
+ return {
1327
+ handled: true,
1328
+ nextI: k
1329
+ };
1330
+ }
1331
+ return {
1332
+ handled: false,
1333
+ nextI: i
1334
+ };
1335
+ };
1336
+ const maskJs = (content, maskStrings) => {
1337
+ const out = content.split("");
1338
+ const len = content.length;
1339
+ const tplStack = [];
1340
+ let i = 0;
1341
+ const mask = (start, end) => {
1342
+ for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
1343
+ };
1344
+ while (i < len) {
1345
+ const c = content[i];
1346
+ if (tplStack.length > 0) {
1347
+ if (c === "{") {
1348
+ tplStack[tplStack.length - 1]++;
1349
+ i++;
1350
+ continue;
1351
+ }
1352
+ if (c === "}") {
1353
+ if (tplStack[tplStack.length - 1] === 0) {
1354
+ tplStack.pop();
1355
+ const scan = consumeTemplateString(content, i + 1);
1356
+ if (maskStrings) mask(i + 1, scan.maskEnd);
1357
+ if (scan.openedInterp) tplStack.push(0);
1358
+ i = scan.resumeAt;
1359
+ continue;
1360
+ }
1361
+ tplStack[tplStack.length - 1]--;
1362
+ i++;
1363
+ continue;
1364
+ }
1365
+ }
1366
+ const handled = handleQuotesAndComments(content, i, tplStack, mask, maskStrings);
1367
+ if (handled.handled) {
1368
+ i = handled.nextI;
1369
+ continue;
1370
+ }
1371
+ i++;
1372
+ }
1373
+ return out.join("");
1374
+ };
1375
+ const consumeQuotedString = (content, start, quote) => {
1376
+ const len = content.length;
1377
+ let i = start + 1;
1378
+ while (i < len) {
1379
+ const c = content[i];
1380
+ if (c === "\\" && i + 1 < len) {
1381
+ i += 2;
1382
+ continue;
1383
+ }
1384
+ if (c === quote) return i + 1;
1385
+ if (c === "\n") return i;
1386
+ i++;
1387
+ }
1388
+ return i;
1389
+ };
1390
+ const consumeTemplateString = (content, start) => {
1391
+ const len = content.length;
1392
+ let i = start;
1393
+ while (i < len) {
1394
+ const c = content[i];
1395
+ if (c === "\\" && i + 1 < len) {
1396
+ i += 2;
1397
+ continue;
1398
+ }
1399
+ if (c === "`") return {
1400
+ maskEnd: i,
1401
+ resumeAt: i + 1,
1402
+ openedInterp: false
1403
+ };
1404
+ if (c === "$" && content[i + 1] === "{") return {
1405
+ maskEnd: i,
1406
+ resumeAt: i + 2,
1407
+ openedInterp: true
1408
+ };
1409
+ i++;
1410
+ }
1411
+ return {
1412
+ maskEnd: i,
1413
+ resumeAt: i,
1414
+ openedInterp: false
1415
+ };
1416
+ };
1417
+ const maskSimple = (content, family, maskStrings) => {
1418
+ const out = content.split("");
1419
+ const len = content.length;
1420
+ let i = 0;
1421
+ const mask = (start, end) => {
1422
+ for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
1423
+ };
1424
+ while (i < len) {
1425
+ const c = content[i];
1426
+ const next = content[i + 1];
1427
+ if (family === "py" && (c === "\"" || c === "'")) {
1428
+ if (content[i + 1] === c && content[i + 2] === c) {
1429
+ const triple = c + c + c;
1430
+ const end = content.indexOf(triple, i + 3);
1431
+ const stop = end === -1 ? len : end + 3;
1432
+ if (maskStrings) mask(i + 3, stop - 3);
1433
+ i = stop;
1434
+ continue;
1435
+ }
1436
+ }
1437
+ if (c === "\"" || c === "'") {
1438
+ const strStart = i;
1439
+ i = consumeQuotedString(content, i, c);
1440
+ if (maskStrings) mask(strStart + 1, i - 1);
1441
+ continue;
1442
+ }
1443
+ if ((family === "py" || family === "rb" || family === "php") && c === "#") {
1444
+ const strStart = i;
1445
+ while (i < len && content[i] !== "\n") i++;
1446
+ mask(strStart, i);
1447
+ continue;
1448
+ }
1449
+ if (family === "php" && c === "/" && next === "/") {
1450
+ const strStart = i;
1451
+ while (i < len && content[i] !== "\n") i++;
1452
+ mask(strStart, i);
1453
+ continue;
1454
+ }
1455
+ if (family === "php" && c === "/" && next === "*") {
1456
+ const strStart = i;
1457
+ i += 2;
1458
+ while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
1459
+ if (i < len - 1) i += 2;
1460
+ mask(strStart, i);
1461
+ continue;
1462
+ }
1463
+ i++;
1464
+ }
1465
+ return out.join("");
1466
+ };
1467
+
1468
+ //#endregion
1469
+ //#region src/engines/ai-slop/abstractions.ts
1470
+ const JS_EXTS$1 = new Set([
1471
+ ".ts",
1472
+ ".tsx",
1473
+ ".js",
1474
+ ".jsx",
1475
+ ".mjs",
1476
+ ".cjs"
1477
+ ]);
1266
1478
  const THIN_WRAPPER_PATTERNS = [
1267
1479
  {
1268
1480
  pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
1269
- extensions: JS_EXTS$2
1481
+ extensions: JS_EXTS$1
1270
1482
  },
1271
1483
  {
1272
1484
  pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
1273
- extensions: JS_EXTS$2
1485
+ extensions: JS_EXTS$1
1274
1486
  },
1275
1487
  {
1276
1488
  pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
@@ -1280,14 +1492,16 @@ const THIN_WRAPPER_PATTERNS = [
1280
1492
  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+/];
1281
1493
  const FRAMEWORK_METHOD_NAMES = /^(?:setUp|tearDown|setUpClass|tearDownClass|setUpModule|tearDownModule)$/;
1282
1494
  const DUNDER_PATTERN = /^__\w+__$/;
1283
- const hasHardcodedArgs = (matchText) => {
1284
- const innerCallMatch = matchText.match(/=>\s*\w+\(([^)]*)\)\s*;?\s*$/);
1285
- if (!innerCallMatch) {
1286
- const returnCallMatch = matchText.match(/return\s+\w+\(([^)]*)\)\s*;?\s*\}/);
1287
- if (!returnCallMatch) return false;
1288
- return /['"`]\w+['"`]|(?<!\w)\d+(?!\w)/.test(returnCallMatch[1]);
1289
- }
1290
- return /['"`]\w+['"`]|(?<!\w)\d+(?!\w)/.test(innerCallMatch[1]);
1495
+ const stripParam = (p) => p.trim().split(/[:=]/)[0].trim().replace(/^[*&]+/, "");
1496
+ const paramNames = (paramsText) => new Set(paramsText.split(",").map(stripParam).filter((p) => p && p !== "self" && p !== "cls"));
1497
+ const isIdentityForward = (matchText) => {
1498
+ const paramsMatch = matchText.match(/\(([^)]*)\)/);
1499
+ const innerMatch = matchText.match(/(?:return\s+\w+|=>\s*\w+)\s*\(([^)]*)\)/);
1500
+ if (!paramsMatch || !innerMatch) return false;
1501
+ const params = paramNames(paramsMatch[1]);
1502
+ const args = innerMatch[1].split(",").map((a) => a.trim()).filter((a) => a.length > 0);
1503
+ if (args.length === 0) return false;
1504
+ return args.every((a) => /^[A-Za-z_$][\w$]*$/.test(a) && params.has(a));
1291
1505
  };
1292
1506
  const isUseContextWrapper = (matchText) => /\buse\w+/.test(matchText) && /useContext\s*\(/.test(matchText);
1293
1507
  const detectThinWrappers = (content, relativePath, ext) => {
@@ -1307,7 +1521,7 @@ const detectThinWrappers = (content, relativePath, ext) => {
1307
1521
  const prevLine = lines[lineNumber - 2]?.trim();
1308
1522
  if (prevLine && prevLine.startsWith("@")) continue;
1309
1523
  }
1310
- if (hasHardcodedArgs(matchText)) continue;
1524
+ if (!isIdentityForward(matchText)) continue;
1311
1525
  if (isUseContextWrapper(matchText)) continue;
1312
1526
  diagnostics.push({
1313
1527
  filePath: relativePath,
@@ -1361,8 +1575,9 @@ const detectOverAbstraction = async (context) => {
1361
1575
  }
1362
1576
  const relativePath = path.relative(context.rootDirectory, filePath);
1363
1577
  const ext = path.extname(filePath);
1364
- diagnostics.push(...detectThinWrappers(content, relativePath, ext));
1365
- diagnostics.push(...detectAiNaming(content, relativePath));
1578
+ const codeOnly = maskComments(content, ext);
1579
+ diagnostics.push(...detectThinWrappers(codeOnly, relativePath, ext));
1580
+ diagnostics.push(...detectAiNaming(codeOnly, relativePath));
1366
1581
  }
1367
1582
  return diagnostics;
1368
1583
  };
@@ -1392,8 +1607,7 @@ const JUSTIFICATION_OPENERS = [
1392
1607
  /^(?:First|Then|Finally|Next|Lastly|Subsequently),?\s+(?:it|we|the\s+(?:function|method|class))\b/i
1393
1608
  ];
1394
1609
  const EXPLANATORY_OPENERS = /^(Matches|Detects|Represents|Holds|Stores|Tracks|Handles|Manages|Controls|Contains|Captures|Encapsulates|Wraps|Describes)\s+[A-Za-z`'"]/;
1395
- 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|$)/;
1396
- 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;
1610
+ 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;
1397
1611
  const MEANINGFUL_JSDOC_TAGS = new Set([
1398
1612
  "deprecated",
1399
1613
  "see",
@@ -1489,7 +1703,7 @@ const PHP_DECL_START = /^\s*(?:(?:public|private|protected|static|final|abstract
1489
1703
 
1490
1704
  //#endregion
1491
1705
  //#region src/engines/ai-slop/non-production-paths.ts
1492
- 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;
1706
+ 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;
1493
1707
  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;
1494
1708
  const isNonProductionPath = (relativePath) => DIR_PATTERN.test(relativePath) || BASENAME_PATTERN.test(relativePath);
1495
1709
 
@@ -1498,7 +1712,7 @@ const isNonProductionPath = (relativePath) => DIR_PATTERN.test(relativePath) ||
1498
1712
  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";
1499
1713
  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")];
1500
1714
  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")];
1501
- const EXPLANATORY_KEYWORDS = /\b(?:because|since|note|todo|fixme|hack|warn|warning|workaround|caveat|important|assumes?)\b/i;
1715
+ 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;
1502
1716
  const COMMENTED_CODE_CHARS = /[({=;}\]>]/;
1503
1717
  const MAX_TRIVIAL_COMMENT_LENGTH = 60;
1504
1718
  const isJsComment = (trimmed) => trimmed.startsWith("//") && !trimmed.startsWith("///") && !trimmed.startsWith("//!");
@@ -1647,6 +1861,7 @@ const TODO_PATTERN = new RegExp(`\\b(?:${[
1647
1861
  "PLACEHOLDER",
1648
1862
  "STUB"
1649
1863
  ].join("|")})[:\\s]`);
1864
+ const TODO_TRACKING_RE = /https?:\/\/|#\d+|\bgh-\d+\b|\b[A-Z][A-Z0-9]+-\d+\b|\b(?:issue|ticket|jira)\b/i;
1650
1865
  const isBlockCloserAfterReturn = (line) => line.startsWith("}") || line.startsWith("};") || line.startsWith("),") || line.startsWith(");") || line.startsWith("],") || line.startsWith("]);");
1651
1866
  const isGuardedSingleLineExit = (lines, lineIndex) => {
1652
1867
  const contextLines = [];
@@ -1666,7 +1881,10 @@ const detectTodoStubs = (content, relativePath) => {
1666
1881
  for (let i = 0; i < lines.length; i++) {
1667
1882
  const trimmed = lines[i].trim();
1668
1883
  if (!trimmed.startsWith("//") && !trimmed.startsWith("#") && !trimmed.startsWith("*") && !trimmed.startsWith("/*")) continue;
1669
- 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));
1884
+ if (TODO_PATTERN.test(trimmed)) {
1885
+ if (TODO_TRACKING_RE.test(trimmed)) continue;
1886
+ 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));
1887
+ }
1670
1888
  }
1671
1889
  return diagnostics;
1672
1890
  };
@@ -1715,9 +1933,10 @@ const detectDeadPatterns = async (context) => {
1715
1933
  }
1716
1934
  const ext = path.extname(filePath);
1717
1935
  const relativePath = path.relative(context.rootDirectory, filePath);
1718
- diagnostics.push(...detectConsoleLeftovers(content, relativePath, ext));
1936
+ const codeOnly = maskComments(content, ext);
1937
+ diagnostics.push(...detectConsoleLeftovers(codeOnly, relativePath, ext));
1719
1938
  diagnostics.push(...detectTodoStubs(content, relativePath));
1720
- diagnostics.push(...detectDeadCodePatterns(content, relativePath, ext));
1939
+ diagnostics.push(...detectDeadCodePatterns(codeOnly, relativePath, ext));
1721
1940
  diagnostics.push(...detectUnsafeTypePatterns(content, relativePath, ext));
1722
1941
  }
1723
1942
  return diagnostics;
@@ -1907,6 +2126,7 @@ const JS_EXTENSIONS$3 = new Set([
1907
2126
  const IMPORT_FROM_RE$1 = /^\s*import\s+([^;]*?)\s+from\s+["']([^"']+)["']/;
1908
2127
  const TYPE_ONLY_RE = /^\s*type\b/;
1909
2128
  const VALUE_BINDING_RE = /\{([^}]*)\}/;
2129
+ const NAMESPACE_RE = /\*\s+as\s+/;
1910
2130
  const isTypeOnly = (clause) => {
1911
2131
  if (TYPE_ONLY_RE.test(clause)) return true;
1912
2132
  const braces = VALUE_BINDING_RE.exec(clause);
@@ -1924,7 +2144,8 @@ const extractImportLines = (content) => {
1924
2144
  results.push({
1925
2145
  spec: match[2],
1926
2146
  line: i + 1,
1927
- typeOnly: isTypeOnly(match[1])
2147
+ typeOnly: isTypeOnly(match[1]),
2148
+ namespace: NAMESPACE_RE.test(match[1])
1928
2149
  });
1929
2150
  }
1930
2151
  return results;
@@ -1941,11 +2162,11 @@ const detectDuplicateImports = async (context) => {
1941
2162
  } catch {
1942
2163
  continue;
1943
2164
  }
1944
- const imports = extractImportLines(content);
2165
+ const imports = extractImportLines(maskComments(content, path.extname(filePath)));
1945
2166
  if (imports.length < 2) continue;
1946
2167
  const byBucket = /* @__PURE__ */ new Map();
1947
2168
  for (const imp of imports) {
1948
- const key = `${imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
2169
+ const key = `${imp.namespace ? "ns" : imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
1949
2170
  const list = byBucket.get(key) ?? [];
1950
2171
  list.push(imp);
1951
2172
  byBucket.set(key, list);
@@ -2192,6 +2413,30 @@ const LOOPBACK_HOSTS = new Set([
2192
2413
  "0.0.0.0",
2193
2414
  "::1"
2194
2415
  ]);
2416
+ const VENDOR_API_DOMAINS = [
2417
+ "github.com",
2418
+ "githubusercontent.com",
2419
+ "googleapis.com",
2420
+ "accounts.google.com",
2421
+ "stripe.com",
2422
+ "openai.com",
2423
+ "anthropic.com",
2424
+ "slack.com",
2425
+ "twilio.com",
2426
+ "sendgrid.com",
2427
+ "mailgun.net",
2428
+ "cloudflare.com",
2429
+ "discord.com",
2430
+ "telegram.org",
2431
+ "login.microsoftonline.com",
2432
+ "graph.microsoft.com",
2433
+ "twitter.com",
2434
+ "x.com",
2435
+ "twimg.com",
2436
+ "t.co",
2437
+ "api.telegram.org"
2438
+ ];
2439
+ const isVendorApiHost = (host) => VENDOR_API_DOMAINS.some((d) => host === d || host.endsWith(`.${d}`));
2195
2440
  const PLACEHOLDER_ID_RE = /^(?:changeme|replace[_-]?me|your[_-]|example|placeholder|todo)/i;
2196
2441
  const HARDCODED_URL_FINDING = {
2197
2442
  rule: "ai-slop/hardcoded-url",
@@ -2236,6 +2481,7 @@ const shouldFlagUrlLiteral = (line, urlText) => {
2236
2481
  if (!host) return false;
2237
2482
  if (PLACEHOLDER_HOSTS.has(host)) return false;
2238
2483
  if (LOOPBACK_HOSTS.has(host)) return false;
2484
+ if (isVendorApiHost(host)) return false;
2239
2485
  if (DOC_URL_CONTEXT_RE.test(line) && !ENVIRONMENT_HOST_RE.test(host)) return false;
2240
2486
  return URL_CONFIG_CONTEXT_RE.test(line) || ENVIRONMENT_HOST_RE.test(host);
2241
2487
  };
@@ -2287,7 +2533,7 @@ const detectHardcodedConfigLiterals = async (context) => {
2287
2533
  }
2288
2534
  const relativePath = path.relative(context.rootDirectory, filePath);
2289
2535
  const ext = path.extname(filePath);
2290
- diagnostics.push(...scanFileForConfigLiterals(content, relativePath, ext));
2536
+ diagnostics.push(...scanFileForConfigLiterals(maskComments(content, ext), relativePath, ext));
2291
2537
  }
2292
2538
  return diagnostics;
2293
2539
  };
@@ -2420,7 +2666,9 @@ const PYTHON_STDLIB = new Set([
2420
2666
  "builtins",
2421
2667
  "bz2",
2422
2668
  "calendar",
2669
+ "code",
2423
2670
  "codecs",
2671
+ "codeop",
2424
2672
  "collections",
2425
2673
  "concurrent",
2426
2674
  "configparser",
@@ -2493,6 +2741,7 @@ const PYTHON_STDLIB = new Set([
2493
2741
  "readline",
2494
2742
  "reprlib",
2495
2743
  "resource",
2744
+ "rlcompleter",
2496
2745
  "secrets",
2497
2746
  "select",
2498
2747
  "selectors",
@@ -2681,6 +2930,8 @@ const collectFromPyproject = (rootDir, pyDeps) => {
2681
2930
  }
2682
2931
  const extras = content.match(/\[project\.optional-dependencies\]([\s\S]*?)(?=\n\[|$)/);
2683
2932
  if (extras) for (const m of extras[1].matchAll(/["']\s*([a-zA-Z][a-zA-Z0-9_\-.]+)/g)) addPyDep(pyDeps, m[1]);
2933
+ const groups = content.match(/\[dependency-groups\]([\s\S]*?)(?=\n\[[^[]|$)/);
2934
+ if (groups) for (const m of groups[1].matchAll(/["']\s*([a-zA-Z][a-zA-Z0-9_\-.]+)/g)) addPyDep(pyDeps, m[1]);
2684
2935
  const poetryRe = /\[tool\.poetry(?:\.group\.[a-z]+)?\.dependencies\]([\s\S]*?)(?=\n\[|$)/g;
2685
2936
  let match = poetryRe.exec(content);
2686
2937
  while (match !== null) {
@@ -2913,9 +3164,28 @@ const extractJsImports = (content) => {
2913
3164
  const extractPyImports = (content) => {
2914
3165
  const lines = content.split("\n");
2915
3166
  const results = [];
3167
+ let inDoc = null;
3168
+ let typeCheckIndent = -1;
2916
3169
  for (let i = 0; i < lines.length; i++) {
2917
- const line = lines[i].trim();
2918
- if (line.startsWith("#")) continue;
3170
+ const raw = lines[i];
3171
+ const line = raw.trim();
3172
+ if (inDoc) {
3173
+ if (line.includes(inDoc)) inDoc = null;
3174
+ continue;
3175
+ }
3176
+ if (line === "" || line.startsWith("#")) continue;
3177
+ const triples = line.match(/"""|'''/g);
3178
+ if (triples) {
3179
+ if (triples.length % 2 === 1) inDoc = triples[triples.length - 1];
3180
+ continue;
3181
+ }
3182
+ const indent = raw.length - raw.trimStart().length;
3183
+ if (typeCheckIndent >= 0 && indent <= typeCheckIndent) typeCheckIndent = -1;
3184
+ if (/^if\s+(?:[\w.]+\.)?TYPE_CHECKING\b/.test(line)) {
3185
+ typeCheckIndent = indent;
3186
+ continue;
3187
+ }
3188
+ if (typeCheckIndent >= 0) continue;
2919
3189
  const fromMatch = line.match(/^from\s+([\w.]+)\s+import\b/);
2920
3190
  if (fromMatch && !fromMatch[1].startsWith(".")) {
2921
3191
  results.push({
@@ -2925,8 +3195,8 @@ const extractPyImports = (content) => {
2925
3195
  continue;
2926
3196
  }
2927
3197
  const importMatch = line.match(/^import\s+([\w.,\s]+?)(?:\s+as\s+\w+)?\s*$/);
2928
- if (importMatch) for (const raw of importMatch[1].split(",")) {
2929
- const cleaned = raw.trim().split(/\s+as\s+/)[0];
3198
+ if (importMatch) for (const part of importMatch[1].split(",")) {
3199
+ const cleaned = part.trim().split(/\s+as\s+/)[0];
2930
3200
  if (cleaned && !cleaned.startsWith(".")) results.push({
2931
3201
  spec: cleaned,
2932
3202
  line: i + 1
@@ -2983,6 +3253,7 @@ const detectHallucinatedImports = async (context) => {
2983
3253
  continue;
2984
3254
  }
2985
3255
  const relPath = path.relative(context.rootDirectory, filePath);
3256
+ if (isNonProductionPath(relPath)) continue;
2986
3257
  const imports = isJs ? extractJsImports(content) : extractPyImports(content);
2987
3258
  for (const { spec, line } of imports) {
2988
3259
  const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
@@ -3110,7 +3381,7 @@ const collectBlocks = (sourceLines, syntax) => {
3110
3381
  //#endregion
3111
3382
  //#region src/engines/ai-slop/meta-comment.ts
3112
3383
  const PLAN_REFERENCE_RES = [
3113
- /\b(?:stage|step|phase)\s+\d+\b(?!\s*[:.]?\s*(?:bytes|ms|seconds|of\s+\d))/i,
3384
+ /^(?:stage|step|phase)\s+\d+\s*[:.\-–—]/i,
3114
3385
  /\bstep\s+\d+\s+of\s+the\s+plan\b/i,
3115
3386
  /\bas\s+(?:per|requested)\s+(?:the\s+)?(?:requirements?|spec|task|ticket|prompt|instructions?)\b/i,
3116
3387
  /\bper\s+the\s+(?:spec|requirements?|task|ticket|plan|prompt|instructions?)\b/i,
@@ -3212,24 +3483,6 @@ const looksLikeLicenseHeader = (block) => {
3212
3483
  const text = block.rawLines.join(" ").toLowerCase();
3213
3484
  return text.includes("copyright") || text.includes("license") || text.includes("spdx-license-identifier");
3214
3485
  };
3215
- const BARE_LABEL_RE = /^[A-Z][A-Za-z0-9 ]{1,28}$/;
3216
- const isBareSectionLabel = (prose) => {
3217
- if (!BARE_LABEL_RE.test(prose)) return false;
3218
- if (prose.endsWith(".")) return false;
3219
- if (prose.split(/\s+/).length > 3) return false;
3220
- if (STEP_COMMENT_VERB_RE.test(prose)) return false;
3221
- return true;
3222
- };
3223
- const DATA_ENTRY_START = /^\s*(?:\{|\[|["'`]|\d|\w+:\s|case\s)/;
3224
- const nextLineLooksLikeDataEntry = (nextLine) => {
3225
- if (nextLine === null) return false;
3226
- if (!DATA_ENTRY_START.test(nextLine)) return false;
3227
- const trimmed = nextLine.trim();
3228
- if (trimmed.startsWith("case ")) return true;
3229
- if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("\"") || trimmed.startsWith("'") || trimmed.startsWith("`")) return true;
3230
- if (/^\w+\s*:/.test(trimmed)) return true;
3231
- return false;
3232
- };
3233
3486
  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));
3234
3487
  const GO_DECL_NAME_RE = /^(?:func|type|var|const)\s+(?:\([^)]*\)\s*)?(\w+)/;
3235
3488
  const GO_FIELD_LEAD_RE = /^(\w+)\s+/;
@@ -3318,10 +3571,6 @@ const detectNarrativeInBlock = (block, ext) => {
3318
3571
  matched: true,
3319
3572
  reason: "phase/section header"
3320
3573
  };
3321
- if (block.kind === "line" && block.prose.length === 1 && isBareSectionLabel(block.prose[0]) && !nextLineLooksLikeDataEntry(block.nextNonBlankLine) && !looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
3322
- matched: true,
3323
- reason: "bare section label"
3324
- };
3325
3574
  const joined = block.prose.join(" ");
3326
3575
  const hasWhyMarker = EXPLANATORY_WHY_MARKERS.test(joined);
3327
3576
  if (hasWhyMarker || hasDocIndicator(block)) return {
@@ -3352,17 +3601,11 @@ const detectNarrativeInBlock = (block, ext) => {
3352
3601
  };
3353
3602
  const nonEmptyProseCount = block.prose.filter((l) => l.length > 0).length;
3354
3603
  const isAboveDeclaration = looksLikeDeclarationPreamble(block.nextNonBlankLine, ext);
3355
- if (nonEmptyProseCount >= 5) {
3356
- if (isAboveDeclaration) return {
3357
- matched: false,
3358
- reason: ""
3359
- };
3360
- return {
3361
- matched: true,
3362
- reason: "long narrative block"
3363
- };
3364
- }
3365
- if (nonEmptyProseCount >= 3 && !hasWhyMarker && block.kind === "line" && !isAboveDeclaration) return {
3604
+ if (nonEmptyProseCount >= 5 && !isAboveDeclaration && hasPreambleSlopSignal(block)) return {
3605
+ matched: true,
3606
+ reason: "long narrative block"
3607
+ };
3608
+ if (nonEmptyProseCount >= 3 && !hasWhyMarker && block.kind === "line" && !isAboveDeclaration && hasPreambleSlopSignal(block)) return {
3366
3609
  matched: true,
3367
3610
  reason: "multi-line narrative prose"
3368
3611
  };
@@ -3796,7 +4039,7 @@ const detectRustPatterns = async (context) => {
3796
4039
 
3797
4040
  //#endregion
3798
4041
  //#region src/engines/ai-slop/silent-recovery.ts
3799
- const JS_EXTS$1 = new Set([
4042
+ const JS_EXTS = new Set([
3800
4043
  ".ts",
3801
4044
  ".tsx",
3802
4045
  ".js",
@@ -3804,7 +4047,14 @@ const JS_EXTS$1 = new Set([
3804
4047
  ".mjs",
3805
4048
  ".cjs"
3806
4049
  ]);
3807
- const CATCH_HEAD_RE = /\bcatch\s*(?:\([^)]*\))?\s*\{/g;
4050
+ const CATCH_HEAD_RE = /\bcatch\s*(?:\(\s*([^)]*?)\s*\))?\s*\{/g;
4051
+ const isIdentifier = (s) => /^[A-Za-z_$][\w$]*$/.test(s);
4052
+ const recoveryDropsError = (binding, body) => {
4053
+ const name = binding?.trim() ?? "";
4054
+ if (name === "") return true;
4055
+ if (!isIdentifier(name)) return false;
4056
+ return !new RegExp(`\\b${name}\\b`).test(body);
4057
+ };
3808
4058
  const LOG_STATEMENT_RE = /^(?:console|[\w$]+(?:\.[\w$]+)*)\.(?:log|info|warn|warning|error|debug|trace)\s*\(/;
3809
4059
  const HANDLING_TOKEN_RE = /\b(?:throw|return|reject|next|process\.exit|continue|break)\b/;
3810
4060
  const stripBlockComments = (text) => text.replace(/\/\*[\s\S]*?\*\//g, "");
@@ -3854,14 +4104,15 @@ const detectJsSilentRecovery = (content, relPath) => {
3854
4104
  const body = extractCatchBody(content, match.index + match[0].length - 1);
3855
4105
  if (body === null) continue;
3856
4106
  if (!isLogOnlyBody(body)) continue;
4107
+ if (!recoveryDropsError(match[1], body)) continue;
3857
4108
  const line = content.slice(0, match.index).split("\n").length;
3858
4109
  out.push({
3859
4110
  filePath: relPath,
3860
4111
  engine: "ai-slop",
3861
4112
  rule: "ai-slop/silent-recovery",
3862
4113
  severity: "warning",
3863
- message: "Catch only logs then continues, leaving execution in a possibly broken state",
3864
- help: "Handle the error: rethrow, return an error value, or recover explicitly. Logging alone lets the program proceed as if nothing failed.",
4114
+ message: "Catch logs without the caught error then continues; the failure cause is lost",
4115
+ help: "Include the caught error in the log, or rethrow / recover explicitly, so the failure stays diagnosable.",
3865
4116
  line,
3866
4117
  column: 0,
3867
4118
  category: "AI Slop",
@@ -3871,6 +4122,7 @@ const detectJsSilentRecovery = (content, relPath) => {
3871
4122
  return out;
3872
4123
  };
3873
4124
  const PY_EXCEPT_RE = /^(\s*)except\b[^\n]*:\s*(?:#.*)?$/;
4125
+ const PY_EXCEPT_BINDING_RE = /\bas\s+(\w+)\s*:/;
3874
4126
  const PY_LOG_STATEMENT_RE = /^(?:logging|logger|log|self\.log|self\.logger|print)(?:\.(?:debug|info|warning|warn|error|exception|critical))?\s*\(/;
3875
4127
  const PY_HANDLING_TOKEN_RE = /^(?:raise\b|return\b|continue\b|break\b|self\.|[\w.]+\s*=)/;
3876
4128
  const detectPySilentRecovery = (content, relPath) => {
@@ -3894,13 +4146,14 @@ const detectPySilentRecovery = (content, relPath) => {
3894
4146
  const allLogs = bodyLines.every((line) => PY_LOG_STATEMENT_RE.test(line) || /^[\w"'(),.\s+:%{}[\]-]+$/.test(line));
3895
4147
  const sawLog = bodyLines.some((line) => PY_LOG_STATEMENT_RE.test(line));
3896
4148
  if (!allLogs || !sawLog) continue;
4149
+ if (!recoveryDropsError(PY_EXCEPT_BINDING_RE.exec(lines[i])?.[1], bodyLines.join(" "))) continue;
3897
4150
  out.push({
3898
4151
  filePath: relPath,
3899
4152
  engine: "ai-slop",
3900
4153
  rule: "ai-slop/silent-recovery",
3901
4154
  severity: "warning",
3902
- message: "except only logs then continues, leaving execution in a possibly broken state",
3903
- help: "Handle the error: re-raise, return an error value, or recover explicitly. Logging alone lets the program proceed as if nothing failed.",
4155
+ message: "except logs without the caught error then continues; the failure cause is lost",
4156
+ help: "Include the caught error in the log, or re-raise / recover explicitly, so the failure stays diagnosable.",
3904
4157
  line: i + 1,
3905
4158
  column: 0,
3906
4159
  category: "AI Slop",
@@ -3915,7 +4168,7 @@ const detectSilentRecovery = async (context) => {
3915
4168
  for (const filePath of files) {
3916
4169
  if (isAutoGenerated(filePath)) continue;
3917
4170
  const ext = path.extname(filePath);
3918
- const isJs = JS_EXTS$1.has(ext);
4171
+ const isJs = JS_EXTS.has(ext);
3919
4172
  if (!isJs && !(ext === ".py")) continue;
3920
4173
  const relPath = path.relative(context.rootDirectory, filePath);
3921
4174
  if (isNonProductionPath(relPath)) continue;
@@ -4003,10 +4256,11 @@ const extractPyImportedSymbols = (lines) => {
4003
4256
  const importLines = /* @__PURE__ */ new Set();
4004
4257
  for (let i = 0; i < lines.length; i++) {
4005
4258
  const trimmed = lines[i].trim();
4006
- const fromMatch = trimmed.match(/^from\s+[\w.]+\s+import\s+(.+)/);
4259
+ const fromMatch = trimmed.match(/^from\s+([\w.]+)\s+import\s+(.+)/);
4007
4260
  if (fromMatch) {
4008
4261
  importLines.add(i);
4009
- const importPart = fromMatch[1].replace(/#.*$/, "").trim();
4262
+ if (fromMatch[1] === "__future__") continue;
4263
+ const importPart = fromMatch[2].replace(/#.*$/, "").trim();
4010
4264
  if (importPart === "*") continue;
4011
4265
  const cleaned = importPart.replace(/[()]/g, "");
4012
4266
  for (const item of cleaned.split(",")) {
@@ -4382,12 +4636,92 @@ const findBraceFunctionEnd = (lines, startIndex) => {
4382
4636
  maxNesting
4383
4637
  };
4384
4638
  };
4385
- const findPythonFunctionEnd = (lines, startIndex) => {
4386
- const baseIndent = lines[startIndex].match(/^(\s*)/)?.[1].length ?? 0;
4387
- let endLine = startIndex;
4639
+ const extractPythonSignature = (lines, startIndex) => {
4640
+ let depth = 0;
4641
+ let started = false;
4642
+ let params = "";
4643
+ for (let j = startIndex; j < lines.length; j++) {
4644
+ const l = lines[j];
4645
+ for (let ci = 0; ci < l.length; ci++) {
4646
+ const ch = l[ci];
4647
+ if (ch === "(") {
4648
+ depth++;
4649
+ if (depth === 1 && !started) {
4650
+ started = true;
4651
+ continue;
4652
+ }
4653
+ } else if (ch === ")") {
4654
+ depth--;
4655
+ if (depth === 0) return {
4656
+ params,
4657
+ sigEndIndex: j
4658
+ };
4659
+ }
4660
+ if (started) params += ch;
4661
+ }
4662
+ if (started) params += " ";
4663
+ }
4664
+ return {
4665
+ params,
4666
+ sigEndIndex: startIndex
4667
+ };
4668
+ };
4669
+ const countPythonParams = (signature) => {
4670
+ let depth = 0;
4671
+ const parts = [];
4672
+ let current = "";
4673
+ for (const ch of signature) {
4674
+ if (ch === "(" || ch === "[" || ch === "{") depth++;
4675
+ else if (ch === ")" || ch === "]" || ch === "}") depth--;
4676
+ if (ch === "," && depth === 0) {
4677
+ parts.push(current);
4678
+ current = "";
4679
+ continue;
4680
+ }
4681
+ current += ch;
4682
+ }
4683
+ parts.push(current);
4684
+ let count = 0;
4685
+ for (const raw of parts) {
4686
+ const p = raw.trim();
4687
+ if (p.length === 0 || p === "*" || p === "/") continue;
4688
+ if (p.startsWith("*")) continue;
4689
+ if (p.includes("=")) continue;
4690
+ const name = p.split(":")[0].trim();
4691
+ if (name === "self" || name === "cls") continue;
4692
+ count++;
4693
+ }
4694
+ return count;
4695
+ };
4696
+ const countPythonBodyCodeLines = (lines, sigEndIndex, endLine) => {
4697
+ let count = 0;
4698
+ let inDoc = false;
4699
+ let delim = "";
4700
+ for (let j = sigEndIndex + 1; j <= endLine && j < lines.length; j++) {
4701
+ const t = lines[j].trim();
4702
+ if (inDoc) {
4703
+ if (t.includes(delim)) inDoc = false;
4704
+ continue;
4705
+ }
4706
+ if (t === "" || t.startsWith("#")) continue;
4707
+ const opener = t.startsWith("\"\"\"") ? "\"\"\"" : t.startsWith("'''") ? "'''" : "";
4708
+ if (opener) {
4709
+ if (!t.slice(3).includes(opener)) {
4710
+ inDoc = true;
4711
+ delim = opener;
4712
+ }
4713
+ continue;
4714
+ }
4715
+ count++;
4716
+ }
4717
+ return count;
4718
+ };
4719
+ const findPythonFunctionEnd = (lines, defIndex, bodyStartIndex) => {
4720
+ const baseIndent = lines[defIndex].match(/^(\s*)/)?.[1].length ?? 0;
4721
+ let endLine = bodyStartIndex;
4388
4722
  let maxNesting = 0;
4389
4723
  const controlIndentStack = [];
4390
- for (let j = startIndex + 1; j < lines.length; j++) {
4724
+ for (let j = bodyStartIndex + 1; j < lines.length; j++) {
4391
4725
  const l = lines[j];
4392
4726
  if (l.trim() === "") {
4393
4727
  endLine = j;
@@ -4409,7 +4743,10 @@ const findPythonFunctionEnd = (lines, startIndex) => {
4409
4743
  };
4410
4744
  };
4411
4745
  const findFunctionEnd = (lines, startIndex, isPython) => {
4412
- if (isPython) return findPythonFunctionEnd(lines, startIndex);
4746
+ if (isPython) {
4747
+ const { sigEndIndex } = extractPythonSignature(lines, startIndex);
4748
+ return findPythonFunctionEnd(lines, startIndex, sigEndIndex);
4749
+ }
4413
4750
  return findBraceFunctionEnd(lines, startIndex);
4414
4751
  };
4415
4752
  const isBlockArrow = (lines, startIndex) => {
@@ -4474,7 +4811,7 @@ const FUNCTION_PATTERNS = [
4474
4811
  ]
4475
4812
  },
4476
4813
  {
4477
- regex: /^\s*def\s+(\w+)\s*\(([^)]*)\)/,
4814
+ regex: /^\s*(?:async\s+)?def\s+(\w+)\s*\(/,
4478
4815
  langFilter: [".py"]
4479
4816
  },
4480
4817
  {
@@ -4531,14 +4868,23 @@ const analyzeFunctions = (content, ext) => {
4531
4868
  const isPython = fnMatch.patternIndex === 2;
4532
4869
  if (fnMatch.patternIndex === 1 && !isBlockArrow(lines, i)) continue;
4533
4870
  const { endLine, maxNesting } = findFunctionEnd(lines, i, isPython);
4534
- const bodyLines = lines.slice(i + 1, endLine);
4535
- const templateLines = isPython ? 0 : countTemplateLines(bodyLines);
4871
+ let templateLines;
4872
+ let paramCount;
4873
+ if (isPython) {
4874
+ const sig = extractPythonSignature(lines, i);
4875
+ const codeLines = countPythonBodyCodeLines(lines, sig.sigEndIndex, endLine);
4876
+ templateLines = endLine - i + 1 - codeLines;
4877
+ paramCount = countPythonParams(sig.params);
4878
+ } else {
4879
+ templateLines = countTemplateLines(lines.slice(i + 1, endLine));
4880
+ paramCount = countParams(fnMatch.params);
4881
+ }
4536
4882
  functions.push({
4537
4883
  name: fnMatch.name,
4538
4884
  startLine: i + 1,
4539
4885
  lineCount: endLine - i + 1,
4540
4886
  maxNesting,
4541
- paramCount: countParams(fnMatch.params),
4887
+ paramCount,
4542
4888
  templateLines
4543
4889
  });
4544
4890
  }
@@ -6550,212 +6896,6 @@ const runCargoAudit = async (rootDir, timeout) => {
6550
6896
  }
6551
6897
  };
6552
6898
 
6553
- //#endregion
6554
- //#region src/utils/source-masker.ts
6555
- const JS_EXTS = new Set([
6556
- ".ts",
6557
- ".tsx",
6558
- ".js",
6559
- ".jsx",
6560
- ".mjs",
6561
- ".cjs"
6562
- ]);
6563
- const PY_EXTS = new Set([".py"]);
6564
- const RB_EXTS = new Set([".rb"]);
6565
- const PHP_EXTS = new Set([".php"]);
6566
- const familyForExt = (ext) => {
6567
- if (JS_EXTS.has(ext)) return "js";
6568
- if (PY_EXTS.has(ext)) return "py";
6569
- if (RB_EXTS.has(ext)) return "rb";
6570
- if (PHP_EXTS.has(ext)) return "php";
6571
- return "none";
6572
- };
6573
- const maskStringsAndComments = (content, ext) => {
6574
- const family = familyForExt(ext);
6575
- if (family === "none") return content;
6576
- if (family === "js") return maskJs(content);
6577
- return maskSimple(content, family);
6578
- };
6579
- const handleQuotesAndComments = (content, i, tplStack, mask) => {
6580
- const len = content.length;
6581
- const c = content[i];
6582
- const next = content[i + 1];
6583
- if (c === "\"" || c === "'") {
6584
- const strStart = i;
6585
- const end = consumeQuotedString(content, i, c);
6586
- mask(strStart + 1, end - 1);
6587
- return {
6588
- handled: true,
6589
- nextI: end
6590
- };
6591
- }
6592
- if (c === "`") {
6593
- const scan = consumeTemplateString(content, i + 1);
6594
- mask(i + 1, scan.maskEnd);
6595
- if (scan.openedInterp) tplStack.push(0);
6596
- return {
6597
- handled: true,
6598
- nextI: scan.resumeAt
6599
- };
6600
- }
6601
- if (c === "/" && next === "/") {
6602
- const strStart = i;
6603
- let k = i;
6604
- while (k < len && content[k] !== "\n") k++;
6605
- mask(strStart, k);
6606
- return {
6607
- handled: true,
6608
- nextI: k
6609
- };
6610
- }
6611
- if (c === "/" && next === "*") {
6612
- const strStart = i;
6613
- let k = i + 2;
6614
- while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
6615
- if (k < len - 1) k += 2;
6616
- mask(strStart, k);
6617
- return {
6618
- handled: true,
6619
- nextI: k
6620
- };
6621
- }
6622
- return {
6623
- handled: false,
6624
- nextI: i
6625
- };
6626
- };
6627
- const maskJs = (content) => {
6628
- const out = content.split("");
6629
- const len = content.length;
6630
- const tplStack = [];
6631
- let i = 0;
6632
- const mask = (start, end) => {
6633
- for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
6634
- };
6635
- while (i < len) {
6636
- const c = content[i];
6637
- if (tplStack.length > 0) {
6638
- if (c === "{") {
6639
- tplStack[tplStack.length - 1]++;
6640
- i++;
6641
- continue;
6642
- }
6643
- if (c === "}") {
6644
- if (tplStack[tplStack.length - 1] === 0) {
6645
- tplStack.pop();
6646
- const scan = consumeTemplateString(content, i + 1);
6647
- mask(i + 1, scan.maskEnd);
6648
- if (scan.openedInterp) tplStack.push(0);
6649
- i = scan.resumeAt;
6650
- continue;
6651
- }
6652
- tplStack[tplStack.length - 1]--;
6653
- i++;
6654
- continue;
6655
- }
6656
- }
6657
- const handled = handleQuotesAndComments(content, i, tplStack, mask);
6658
- if (handled.handled) {
6659
- i = handled.nextI;
6660
- continue;
6661
- }
6662
- i++;
6663
- }
6664
- return out.join("");
6665
- };
6666
- const consumeQuotedString = (content, start, quote) => {
6667
- const len = content.length;
6668
- let i = start + 1;
6669
- while (i < len) {
6670
- const c = content[i];
6671
- if (c === "\\" && i + 1 < len) {
6672
- i += 2;
6673
- continue;
6674
- }
6675
- if (c === quote) return i + 1;
6676
- if (c === "\n") return i;
6677
- i++;
6678
- }
6679
- return i;
6680
- };
6681
- const consumeTemplateString = (content, start) => {
6682
- const len = content.length;
6683
- let i = start;
6684
- while (i < len) {
6685
- const c = content[i];
6686
- if (c === "\\" && i + 1 < len) {
6687
- i += 2;
6688
- continue;
6689
- }
6690
- if (c === "`") return {
6691
- maskEnd: i,
6692
- resumeAt: i + 1,
6693
- openedInterp: false
6694
- };
6695
- if (c === "$" && content[i + 1] === "{") return {
6696
- maskEnd: i,
6697
- resumeAt: i + 2,
6698
- openedInterp: true
6699
- };
6700
- i++;
6701
- }
6702
- return {
6703
- maskEnd: i,
6704
- resumeAt: i,
6705
- openedInterp: false
6706
- };
6707
- };
6708
- const maskSimple = (content, family) => {
6709
- const out = content.split("");
6710
- const len = content.length;
6711
- let i = 0;
6712
- const mask = (start, end) => {
6713
- for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
6714
- };
6715
- while (i < len) {
6716
- const c = content[i];
6717
- const next = content[i + 1];
6718
- if (family === "py" && (c === "\"" || c === "'")) {
6719
- if (content[i + 1] === c && content[i + 2] === c) {
6720
- const triple = c + c + c;
6721
- const end = content.indexOf(triple, i + 3);
6722
- const stop = end === -1 ? len : end + 3;
6723
- mask(i + 3, stop - 3);
6724
- i = stop;
6725
- continue;
6726
- }
6727
- }
6728
- if (c === "\"" || c === "'") {
6729
- const strStart = i;
6730
- i = consumeQuotedString(content, i, c);
6731
- mask(strStart + 1, i - 1);
6732
- continue;
6733
- }
6734
- if ((family === "py" || family === "rb" || family === "php") && c === "#") {
6735
- const strStart = i;
6736
- while (i < len && content[i] !== "\n") i++;
6737
- mask(strStart, i);
6738
- continue;
6739
- }
6740
- if (family === "php" && c === "/" && next === "/") {
6741
- const strStart = i;
6742
- while (i < len && content[i] !== "\n") i++;
6743
- mask(strStart, i);
6744
- continue;
6745
- }
6746
- if (family === "php" && c === "/" && next === "*") {
6747
- const strStart = i;
6748
- i += 2;
6749
- while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
6750
- if (i < len - 1) i += 2;
6751
- mask(strStart, i);
6752
- continue;
6753
- }
6754
- i++;
6755
- }
6756
- return out.join("");
6757
- };
6758
-
6759
6899
  //#endregion
6760
6900
  //#region src/engines/security/risky.ts
6761
6901
  const ev = "eval";
@@ -7030,6 +7170,7 @@ const scanSecrets = async (context) => {
7030
7170
  } catch {
7031
7171
  continue;
7032
7172
  }
7173
+ content = maskComments(content, path.extname(filePath));
7033
7174
  const relativePath = path.relative(context.rootDirectory, filePath);
7034
7175
  for (const { pattern, name, keywordPrefixed } of SECRET_PATTERNS) {
7035
7176
  const regex = new RegExp(pattern.source, pattern.flags);
@@ -7118,6 +7259,13 @@ const runEngines = async (context, enabledEngines, onStart, onComplete) => {
7118
7259
  //#endregion
7119
7260
  //#region src/scoring/index.ts
7120
7261
  const PERFECT_SCORE = 100;
7262
+ const STYLE_RULES = new Set([
7263
+ "ai-slop/trivial-comment",
7264
+ "ai-slop/narrative-comment",
7265
+ "complexity/file-too-large",
7266
+ "complexity/function-too-long"
7267
+ ]);
7268
+ const STYLE_WEIGHT = .5;
7121
7269
  const getEffectiveFileCount = (diagnostics, sourceFileCount) => {
7122
7270
  if (typeof sourceFileCount === "number" && sourceFileCount > 0) return sourceFileCount;
7123
7271
  const filesWithDiagnostics = new Set(diagnostics.map((d) => d.filePath)).size;
@@ -7132,7 +7280,8 @@ const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoot
7132
7280
  for (const d of diagnostics) {
7133
7281
  const engineWeight = weights[d.engine] ?? 1;
7134
7282
  const severityPenalty = d.severity === "error" ? 3 : d.severity === "warning" ? 1 : .25;
7135
- deductions += severityPenalty * engineWeight;
7283
+ const styleFactor = STYLE_RULES.has(d.rule) ? STYLE_WEIGHT : 1;
7284
+ deductions += severityPenalty * engineWeight * styleFactor;
7136
7285
  }
7137
7286
  const effectiveFileCount = getEffectiveFileCount(diagnostics, sourceFileCount);
7138
7287
  const smoothingConstant = typeof smoothing === "number" ? smoothing : 10;
@@ -8313,12 +8462,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo) => {
8313
8462
  engineTimings
8314
8463
  };
8315
8464
  if (options.sarif) {
8316
- const { buildSarifLog } = await import("./sarif-CLVijBAO.js");
8465
+ const { buildSarifLog } = await import("./sarif-C-vh4wcC.js");
8317
8466
  console.log(JSON.stringify(buildSarifLog(results), null, 2));
8318
8467
  return completion;
8319
8468
  }
8320
8469
  if (options.json) {
8321
- const { buildJsonOutput } = await import("./json-CxiErSgX.js");
8470
+ const { buildJsonOutput } = await import("./json-Bqkcl1DF.js");
8322
8471
  const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
8323
8472
  console.log(JSON.stringify(jsonOut, null, 2));
8324
8473
  return completion;