aislop 0.10.0 → 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/cli.js CHANGED
@@ -34,7 +34,7 @@ var __exportAll = (all, no_symbols) => {
34
34
 
35
35
  //#endregion
36
36
  //#region src/version.ts
37
- const APP_VERSION = "0.10.0";
37
+ const APP_VERSION = "0.10.1";
38
38
 
39
39
  //#endregion
40
40
  //#region src/telemetry/env.ts
@@ -183,7 +183,7 @@ const redactProperties = (props) => {
183
183
  const POSTHOG_HOST = process.env.AISLOP_POSTHOG_HOST ?? "https://eu.i.posthog.com";
184
184
  const POSTHOG_KEY = process.env.AISLOP_POSTHOG_KEY ?? "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
185
185
  const SCHEMA_VERSION = "v2";
186
- const REQUEST_TIMEOUT_MS = 3e3;
186
+ const REQUEST_TIMEOUT_MS$1 = 3e3;
187
187
  const isTelemetryDisabled = (config) => {
188
188
  const env = process.env;
189
189
  if (env.AISLOP_NO_TELEMETRY === "1" || env.DO_NOT_TRACK === "1") return true;
@@ -237,7 +237,7 @@ const track = (input) => {
237
237
  method: "POST",
238
238
  headers: { "Content-Type": "application/json" },
239
239
  body: JSON.stringify(payload),
240
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
240
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS$1)
241
241
  }).then(() => {}).catch(() => {}).finally(() => {
242
242
  pendingRequests.delete(request);
243
243
  });
@@ -836,6 +836,218 @@ const loadConfig = (directory) => {
836
836
  }
837
837
  };
838
838
 
839
+ //#endregion
840
+ //#region src/utils/source-masker.ts
841
+ const JS_EXTS$2 = new Set([
842
+ ".ts",
843
+ ".tsx",
844
+ ".js",
845
+ ".jsx",
846
+ ".mjs",
847
+ ".cjs"
848
+ ]);
849
+ const PY_EXTS = new Set([".py"]);
850
+ const RB_EXTS = new Set([".rb"]);
851
+ const PHP_EXTS = new Set([".php"]);
852
+ const familyForExt = (ext) => {
853
+ if (JS_EXTS$2.has(ext)) return "js";
854
+ if (PY_EXTS.has(ext)) return "py";
855
+ if (RB_EXTS.has(ext)) return "rb";
856
+ if (PHP_EXTS.has(ext)) return "php";
857
+ return "none";
858
+ };
859
+ const maskStringsAndComments = (content, ext) => {
860
+ const family = familyForExt(ext);
861
+ if (family === "none") return content;
862
+ if (family === "js") return maskJs(content, true);
863
+ return maskSimple(content, family, true);
864
+ };
865
+ const maskComments = (content, ext) => {
866
+ const family = familyForExt(ext);
867
+ if (family === "none") return content;
868
+ if (family === "js") return maskJs(content, false);
869
+ return maskSimple(content, family, false);
870
+ };
871
+ const handleQuotesAndComments = (content, i, tplStack, mask, maskStrings) => {
872
+ const len = content.length;
873
+ const c = content[i];
874
+ const next = content[i + 1];
875
+ if (c === "\"" || c === "'") {
876
+ const strStart = i;
877
+ const end = consumeQuotedString(content, i, c);
878
+ if (maskStrings) mask(strStart + 1, end - 1);
879
+ return {
880
+ handled: true,
881
+ nextI: end
882
+ };
883
+ }
884
+ if (c === "`") {
885
+ const scan = consumeTemplateString(content, i + 1);
886
+ if (maskStrings) mask(i + 1, scan.maskEnd);
887
+ if (scan.openedInterp) tplStack.push(0);
888
+ return {
889
+ handled: true,
890
+ nextI: scan.resumeAt
891
+ };
892
+ }
893
+ if (c === "/" && next === "/") {
894
+ const strStart = i;
895
+ let k = i;
896
+ while (k < len && content[k] !== "\n") k++;
897
+ mask(strStart, k);
898
+ return {
899
+ handled: true,
900
+ nextI: k
901
+ };
902
+ }
903
+ if (c === "/" && next === "*") {
904
+ const strStart = i;
905
+ let k = i + 2;
906
+ while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
907
+ if (k < len - 1) k += 2;
908
+ mask(strStart, k);
909
+ return {
910
+ handled: true,
911
+ nextI: k
912
+ };
913
+ }
914
+ return {
915
+ handled: false,
916
+ nextI: i
917
+ };
918
+ };
919
+ const maskJs = (content, maskStrings) => {
920
+ const out = content.split("");
921
+ const len = content.length;
922
+ const tplStack = [];
923
+ let i = 0;
924
+ const mask = (start, end) => {
925
+ for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
926
+ };
927
+ while (i < len) {
928
+ const c = content[i];
929
+ if (tplStack.length > 0) {
930
+ if (c === "{") {
931
+ tplStack[tplStack.length - 1]++;
932
+ i++;
933
+ continue;
934
+ }
935
+ if (c === "}") {
936
+ if (tplStack[tplStack.length - 1] === 0) {
937
+ tplStack.pop();
938
+ const scan = consumeTemplateString(content, i + 1);
939
+ if (maskStrings) mask(i + 1, scan.maskEnd);
940
+ if (scan.openedInterp) tplStack.push(0);
941
+ i = scan.resumeAt;
942
+ continue;
943
+ }
944
+ tplStack[tplStack.length - 1]--;
945
+ i++;
946
+ continue;
947
+ }
948
+ }
949
+ const handled = handleQuotesAndComments(content, i, tplStack, mask, maskStrings);
950
+ if (handled.handled) {
951
+ i = handled.nextI;
952
+ continue;
953
+ }
954
+ i++;
955
+ }
956
+ return out.join("");
957
+ };
958
+ const consumeQuotedString = (content, start, quote) => {
959
+ const len = content.length;
960
+ let i = start + 1;
961
+ while (i < len) {
962
+ const c = content[i];
963
+ if (c === "\\" && i + 1 < len) {
964
+ i += 2;
965
+ continue;
966
+ }
967
+ if (c === quote) return i + 1;
968
+ if (c === "\n") return i;
969
+ i++;
970
+ }
971
+ return i;
972
+ };
973
+ const consumeTemplateString = (content, start) => {
974
+ const len = content.length;
975
+ let i = start;
976
+ while (i < len) {
977
+ const c = content[i];
978
+ if (c === "\\" && i + 1 < len) {
979
+ i += 2;
980
+ continue;
981
+ }
982
+ if (c === "`") return {
983
+ maskEnd: i,
984
+ resumeAt: i + 1,
985
+ openedInterp: false
986
+ };
987
+ if (c === "$" && content[i + 1] === "{") return {
988
+ maskEnd: i,
989
+ resumeAt: i + 2,
990
+ openedInterp: true
991
+ };
992
+ i++;
993
+ }
994
+ return {
995
+ maskEnd: i,
996
+ resumeAt: i,
997
+ openedInterp: false
998
+ };
999
+ };
1000
+ const maskSimple = (content, family, maskStrings) => {
1001
+ const out = content.split("");
1002
+ const len = content.length;
1003
+ let i = 0;
1004
+ const mask = (start, end) => {
1005
+ for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
1006
+ };
1007
+ while (i < len) {
1008
+ const c = content[i];
1009
+ const next = content[i + 1];
1010
+ if (family === "py" && (c === "\"" || c === "'")) {
1011
+ if (content[i + 1] === c && content[i + 2] === c) {
1012
+ const triple = c + c + c;
1013
+ const end = content.indexOf(triple, i + 3);
1014
+ const stop = end === -1 ? len : end + 3;
1015
+ if (maskStrings) mask(i + 3, stop - 3);
1016
+ i = stop;
1017
+ continue;
1018
+ }
1019
+ }
1020
+ if (c === "\"" || c === "'") {
1021
+ const strStart = i;
1022
+ i = consumeQuotedString(content, i, c);
1023
+ if (maskStrings) mask(strStart + 1, i - 1);
1024
+ continue;
1025
+ }
1026
+ if ((family === "py" || family === "rb" || family === "php") && c === "#") {
1027
+ const strStart = i;
1028
+ while (i < len && content[i] !== "\n") i++;
1029
+ mask(strStart, i);
1030
+ continue;
1031
+ }
1032
+ if (family === "php" && c === "/" && next === "/") {
1033
+ const strStart = i;
1034
+ while (i < len && content[i] !== "\n") i++;
1035
+ mask(strStart, i);
1036
+ continue;
1037
+ }
1038
+ if (family === "php" && c === "/" && next === "*") {
1039
+ const strStart = i;
1040
+ i += 2;
1041
+ while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
1042
+ if (i < len - 1) i += 2;
1043
+ mask(strStart, i);
1044
+ continue;
1045
+ }
1046
+ i++;
1047
+ }
1048
+ return out.join("");
1049
+ };
1050
+
839
1051
  //#endregion
840
1052
  //#region src/utils/source-files.ts
841
1053
  const MAX_BUFFER$1 = 50 * 1024 * 1024;
@@ -1086,7 +1298,7 @@ const getSourceFilesWithExtras = (context, extraExtensions) => {
1086
1298
 
1087
1299
  //#endregion
1088
1300
  //#region src/engines/ai-slop/abstractions.ts
1089
- const JS_EXTS$2 = new Set([
1301
+ const JS_EXTS$1 = new Set([
1090
1302
  ".ts",
1091
1303
  ".tsx",
1092
1304
  ".js",
@@ -1097,11 +1309,11 @@ const JS_EXTS$2 = new Set([
1097
1309
  const THIN_WRAPPER_PATTERNS = [
1098
1310
  {
1099
1311
  pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
1100
- extensions: JS_EXTS$2
1312
+ extensions: JS_EXTS$1
1101
1313
  },
1102
1314
  {
1103
1315
  pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
1104
- extensions: JS_EXTS$2
1316
+ extensions: JS_EXTS$1
1105
1317
  },
1106
1318
  {
1107
1319
  pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
@@ -1194,8 +1406,9 @@ const detectOverAbstraction = async (context) => {
1194
1406
  }
1195
1407
  const relativePath = path.relative(context.rootDirectory, filePath);
1196
1408
  const ext = path.extname(filePath);
1197
- diagnostics.push(...detectThinWrappers(content, relativePath, ext));
1198
- diagnostics.push(...detectAiNaming(content, relativePath));
1409
+ const codeOnly = maskComments(content, ext);
1410
+ diagnostics.push(...detectThinWrappers(codeOnly, relativePath, ext));
1411
+ diagnostics.push(...detectAiNaming(codeOnly, relativePath));
1199
1412
  }
1200
1413
  return diagnostics;
1201
1414
  };
@@ -1551,9 +1764,10 @@ const detectDeadPatterns = async (context) => {
1551
1764
  }
1552
1765
  const ext = path.extname(filePath);
1553
1766
  const relativePath = path.relative(context.rootDirectory, filePath);
1554
- diagnostics.push(...detectConsoleLeftovers(content, relativePath, ext));
1767
+ const codeOnly = maskComments(content, ext);
1768
+ diagnostics.push(...detectConsoleLeftovers(codeOnly, relativePath, ext));
1555
1769
  diagnostics.push(...detectTodoStubs(content, relativePath));
1556
- diagnostics.push(...detectDeadCodePatterns(content, relativePath, ext));
1770
+ diagnostics.push(...detectDeadCodePatterns(codeOnly, relativePath, ext));
1557
1771
  diagnostics.push(...detectUnsafeTypePatterns(content, relativePath, ext));
1558
1772
  }
1559
1773
  return diagnostics;
@@ -1743,6 +1957,7 @@ const JS_EXTENSIONS$3 = new Set([
1743
1957
  const IMPORT_FROM_RE$1 = /^\s*import\s+([^;]*?)\s+from\s+["']([^"']+)["']/;
1744
1958
  const TYPE_ONLY_RE = /^\s*type\b/;
1745
1959
  const VALUE_BINDING_RE = /\{([^}]*)\}/;
1960
+ const NAMESPACE_RE = /\*\s+as\s+/;
1746
1961
  const isTypeOnly = (clause) => {
1747
1962
  if (TYPE_ONLY_RE.test(clause)) return true;
1748
1963
  const braces = VALUE_BINDING_RE.exec(clause);
@@ -1760,7 +1975,8 @@ const extractImportLines = (content) => {
1760
1975
  results.push({
1761
1976
  spec: match[2],
1762
1977
  line: i + 1,
1763
- typeOnly: isTypeOnly(match[1])
1978
+ typeOnly: isTypeOnly(match[1]),
1979
+ namespace: NAMESPACE_RE.test(match[1])
1764
1980
  });
1765
1981
  }
1766
1982
  return results;
@@ -1777,11 +1993,11 @@ const detectDuplicateImports = async (context) => {
1777
1993
  } catch {
1778
1994
  continue;
1779
1995
  }
1780
- const imports = extractImportLines(content);
1996
+ const imports = extractImportLines(maskComments(content, path.extname(filePath)));
1781
1997
  if (imports.length < 2) continue;
1782
1998
  const byBucket = /* @__PURE__ */ new Map();
1783
1999
  for (const imp of imports) {
1784
- const key = `${imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
2000
+ const key = `${imp.namespace ? "ns" : imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
1785
2001
  const list = byBucket.get(key) ?? [];
1786
2002
  list.push(imp);
1787
2003
  byBucket.set(key, list);
@@ -2148,7 +2364,7 @@ const detectHardcodedConfigLiterals = async (context) => {
2148
2364
  }
2149
2365
  const relativePath = path.relative(context.rootDirectory, filePath);
2150
2366
  const ext = path.extname(filePath);
2151
- diagnostics.push(...scanFileForConfigLiterals(content, relativePath, ext));
2367
+ diagnostics.push(...scanFileForConfigLiterals(maskComments(content, ext), relativePath, ext));
2152
2368
  }
2153
2369
  return diagnostics;
2154
2370
  };
@@ -3654,7 +3870,7 @@ const detectRustPatterns = async (context) => {
3654
3870
 
3655
3871
  //#endregion
3656
3872
  //#region src/engines/ai-slop/silent-recovery.ts
3657
- const JS_EXTS$1 = new Set([
3873
+ const JS_EXTS = new Set([
3658
3874
  ".ts",
3659
3875
  ".tsx",
3660
3876
  ".js",
@@ -3783,7 +3999,7 @@ const detectSilentRecovery = async (context) => {
3783
3999
  for (const filePath of files) {
3784
4000
  if (isAutoGenerated(filePath)) continue;
3785
4001
  const ext = path.extname(filePath);
3786
- const isJs = JS_EXTS$1.has(ext);
4002
+ const isJs = JS_EXTS.has(ext);
3787
4003
  if (!isJs && !(ext === ".py")) continue;
3788
4004
  const relPath = path.relative(context.rootDirectory, filePath);
3789
4005
  if (isNonProductionPath(relPath)) continue;
@@ -4263,12 +4479,92 @@ const findBraceFunctionEnd = (lines, startIndex) => {
4263
4479
  maxNesting
4264
4480
  };
4265
4481
  };
4266
- const findPythonFunctionEnd = (lines, startIndex) => {
4267
- const baseIndent = lines[startIndex].match(/^(\s*)/)?.[1].length ?? 0;
4268
- let endLine = startIndex;
4482
+ const extractPythonSignature = (lines, startIndex) => {
4483
+ let depth = 0;
4484
+ let started = false;
4485
+ let params = "";
4486
+ for (let j = startIndex; j < lines.length; j++) {
4487
+ const l = lines[j];
4488
+ for (let ci = 0; ci < l.length; ci++) {
4489
+ const ch = l[ci];
4490
+ if (ch === "(") {
4491
+ depth++;
4492
+ if (depth === 1 && !started) {
4493
+ started = true;
4494
+ continue;
4495
+ }
4496
+ } else if (ch === ")") {
4497
+ depth--;
4498
+ if (depth === 0) return {
4499
+ params,
4500
+ sigEndIndex: j
4501
+ };
4502
+ }
4503
+ if (started) params += ch;
4504
+ }
4505
+ if (started) params += " ";
4506
+ }
4507
+ return {
4508
+ params,
4509
+ sigEndIndex: startIndex
4510
+ };
4511
+ };
4512
+ const countPythonParams = (signature) => {
4513
+ let depth = 0;
4514
+ const parts = [];
4515
+ let current = "";
4516
+ for (const ch of signature) {
4517
+ if (ch === "(" || ch === "[" || ch === "{") depth++;
4518
+ else if (ch === ")" || ch === "]" || ch === "}") depth--;
4519
+ if (ch === "," && depth === 0) {
4520
+ parts.push(current);
4521
+ current = "";
4522
+ continue;
4523
+ }
4524
+ current += ch;
4525
+ }
4526
+ parts.push(current);
4527
+ let count = 0;
4528
+ for (const raw of parts) {
4529
+ const p = raw.trim();
4530
+ if (p.length === 0 || p === "*" || p === "/") continue;
4531
+ if (p.startsWith("*")) continue;
4532
+ if (p.includes("=")) continue;
4533
+ const name = p.split(":")[0].trim();
4534
+ if (name === "self" || name === "cls") continue;
4535
+ count++;
4536
+ }
4537
+ return count;
4538
+ };
4539
+ const countPythonBodyCodeLines = (lines, sigEndIndex, endLine) => {
4540
+ let count = 0;
4541
+ let inDoc = false;
4542
+ let delim = "";
4543
+ for (let j = sigEndIndex + 1; j <= endLine && j < lines.length; j++) {
4544
+ const t = lines[j].trim();
4545
+ if (inDoc) {
4546
+ if (t.includes(delim)) inDoc = false;
4547
+ continue;
4548
+ }
4549
+ if (t === "" || t.startsWith("#")) continue;
4550
+ const opener = t.startsWith("\"\"\"") ? "\"\"\"" : t.startsWith("'''") ? "'''" : "";
4551
+ if (opener) {
4552
+ if (!t.slice(3).includes(opener)) {
4553
+ inDoc = true;
4554
+ delim = opener;
4555
+ }
4556
+ continue;
4557
+ }
4558
+ count++;
4559
+ }
4560
+ return count;
4561
+ };
4562
+ const findPythonFunctionEnd = (lines, defIndex, bodyStartIndex) => {
4563
+ const baseIndent = lines[defIndex].match(/^(\s*)/)?.[1].length ?? 0;
4564
+ let endLine = bodyStartIndex;
4269
4565
  let maxNesting = 0;
4270
4566
  const controlIndentStack = [];
4271
- for (let j = startIndex + 1; j < lines.length; j++) {
4567
+ for (let j = bodyStartIndex + 1; j < lines.length; j++) {
4272
4568
  const l = lines[j];
4273
4569
  if (l.trim() === "") {
4274
4570
  endLine = j;
@@ -4290,7 +4586,10 @@ const findPythonFunctionEnd = (lines, startIndex) => {
4290
4586
  };
4291
4587
  };
4292
4588
  const findFunctionEnd = (lines, startIndex, isPython) => {
4293
- if (isPython) return findPythonFunctionEnd(lines, startIndex);
4589
+ if (isPython) {
4590
+ const { sigEndIndex } = extractPythonSignature(lines, startIndex);
4591
+ return findPythonFunctionEnd(lines, startIndex, sigEndIndex);
4592
+ }
4294
4593
  return findBraceFunctionEnd(lines, startIndex);
4295
4594
  };
4296
4595
  const isBlockArrow = (lines, startIndex) => {
@@ -4355,7 +4654,7 @@ const FUNCTION_PATTERNS = [
4355
4654
  ]
4356
4655
  },
4357
4656
  {
4358
- regex: /^\s*def\s+(\w+)\s*\(([^)]*)\)/,
4657
+ regex: /^\s*(?:async\s+)?def\s+(\w+)\s*\(/,
4359
4658
  langFilter: [".py"]
4360
4659
  },
4361
4660
  {
@@ -4412,14 +4711,23 @@ const analyzeFunctions = (content, ext) => {
4412
4711
  const isPython = fnMatch.patternIndex === 2;
4413
4712
  if (fnMatch.patternIndex === 1 && !isBlockArrow(lines, i)) continue;
4414
4713
  const { endLine, maxNesting } = findFunctionEnd(lines, i, isPython);
4415
- const bodyLines = lines.slice(i + 1, endLine);
4416
- const templateLines = isPython ? 0 : countTemplateLines(bodyLines);
4714
+ let templateLines;
4715
+ let paramCount;
4716
+ if (isPython) {
4717
+ const sig = extractPythonSignature(lines, i);
4718
+ const codeLines = countPythonBodyCodeLines(lines, sig.sigEndIndex, endLine);
4719
+ templateLines = endLine - i + 1 - codeLines;
4720
+ paramCount = countPythonParams(sig.params);
4721
+ } else {
4722
+ templateLines = countTemplateLines(lines.slice(i + 1, endLine));
4723
+ paramCount = countParams(fnMatch.params);
4724
+ }
4417
4725
  functions.push({
4418
4726
  name: fnMatch.name,
4419
4727
  startLine: i + 1,
4420
4728
  lineCount: endLine - i + 1,
4421
4729
  maxNesting,
4422
- paramCount: countParams(fnMatch.params),
4730
+ paramCount,
4423
4731
  templateLines
4424
4732
  });
4425
4733
  }
@@ -6629,212 +6937,6 @@ const runCargoAudit = async (rootDir, timeout) => {
6629
6937
  }
6630
6938
  };
6631
6939
 
6632
- //#endregion
6633
- //#region src/utils/source-masker.ts
6634
- const JS_EXTS = new Set([
6635
- ".ts",
6636
- ".tsx",
6637
- ".js",
6638
- ".jsx",
6639
- ".mjs",
6640
- ".cjs"
6641
- ]);
6642
- const PY_EXTS = new Set([".py"]);
6643
- const RB_EXTS = new Set([".rb"]);
6644
- const PHP_EXTS = new Set([".php"]);
6645
- const familyForExt = (ext) => {
6646
- if (JS_EXTS.has(ext)) return "js";
6647
- if (PY_EXTS.has(ext)) return "py";
6648
- if (RB_EXTS.has(ext)) return "rb";
6649
- if (PHP_EXTS.has(ext)) return "php";
6650
- return "none";
6651
- };
6652
- const maskStringsAndComments = (content, ext) => {
6653
- const family = familyForExt(ext);
6654
- if (family === "none") return content;
6655
- if (family === "js") return maskJs(content);
6656
- return maskSimple(content, family);
6657
- };
6658
- const handleQuotesAndComments = (content, i, tplStack, mask) => {
6659
- const len = content.length;
6660
- const c = content[i];
6661
- const next = content[i + 1];
6662
- if (c === "\"" || c === "'") {
6663
- const strStart = i;
6664
- const end = consumeQuotedString(content, i, c);
6665
- mask(strStart + 1, end - 1);
6666
- return {
6667
- handled: true,
6668
- nextI: end
6669
- };
6670
- }
6671
- if (c === "`") {
6672
- const scan = consumeTemplateString(content, i + 1);
6673
- mask(i + 1, scan.maskEnd);
6674
- if (scan.openedInterp) tplStack.push(0);
6675
- return {
6676
- handled: true,
6677
- nextI: scan.resumeAt
6678
- };
6679
- }
6680
- if (c === "/" && next === "/") {
6681
- const strStart = i;
6682
- let k = i;
6683
- while (k < len && content[k] !== "\n") k++;
6684
- mask(strStart, k);
6685
- return {
6686
- handled: true,
6687
- nextI: k
6688
- };
6689
- }
6690
- if (c === "/" && next === "*") {
6691
- const strStart = i;
6692
- let k = i + 2;
6693
- while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
6694
- if (k < len - 1) k += 2;
6695
- mask(strStart, k);
6696
- return {
6697
- handled: true,
6698
- nextI: k
6699
- };
6700
- }
6701
- return {
6702
- handled: false,
6703
- nextI: i
6704
- };
6705
- };
6706
- const maskJs = (content) => {
6707
- const out = content.split("");
6708
- const len = content.length;
6709
- const tplStack = [];
6710
- let i = 0;
6711
- const mask = (start, end) => {
6712
- for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
6713
- };
6714
- while (i < len) {
6715
- const c = content[i];
6716
- if (tplStack.length > 0) {
6717
- if (c === "{") {
6718
- tplStack[tplStack.length - 1]++;
6719
- i++;
6720
- continue;
6721
- }
6722
- if (c === "}") {
6723
- if (tplStack[tplStack.length - 1] === 0) {
6724
- tplStack.pop();
6725
- const scan = consumeTemplateString(content, i + 1);
6726
- mask(i + 1, scan.maskEnd);
6727
- if (scan.openedInterp) tplStack.push(0);
6728
- i = scan.resumeAt;
6729
- continue;
6730
- }
6731
- tplStack[tplStack.length - 1]--;
6732
- i++;
6733
- continue;
6734
- }
6735
- }
6736
- const handled = handleQuotesAndComments(content, i, tplStack, mask);
6737
- if (handled.handled) {
6738
- i = handled.nextI;
6739
- continue;
6740
- }
6741
- i++;
6742
- }
6743
- return out.join("");
6744
- };
6745
- const consumeQuotedString = (content, start, quote) => {
6746
- const len = content.length;
6747
- let i = start + 1;
6748
- while (i < len) {
6749
- const c = content[i];
6750
- if (c === "\\" && i + 1 < len) {
6751
- i += 2;
6752
- continue;
6753
- }
6754
- if (c === quote) return i + 1;
6755
- if (c === "\n") return i;
6756
- i++;
6757
- }
6758
- return i;
6759
- };
6760
- const consumeTemplateString = (content, start) => {
6761
- const len = content.length;
6762
- let i = start;
6763
- while (i < len) {
6764
- const c = content[i];
6765
- if (c === "\\" && i + 1 < len) {
6766
- i += 2;
6767
- continue;
6768
- }
6769
- if (c === "`") return {
6770
- maskEnd: i,
6771
- resumeAt: i + 1,
6772
- openedInterp: false
6773
- };
6774
- if (c === "$" && content[i + 1] === "{") return {
6775
- maskEnd: i,
6776
- resumeAt: i + 2,
6777
- openedInterp: true
6778
- };
6779
- i++;
6780
- }
6781
- return {
6782
- maskEnd: i,
6783
- resumeAt: i,
6784
- openedInterp: false
6785
- };
6786
- };
6787
- const maskSimple = (content, family) => {
6788
- const out = content.split("");
6789
- const len = content.length;
6790
- let i = 0;
6791
- const mask = (start, end) => {
6792
- for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
6793
- };
6794
- while (i < len) {
6795
- const c = content[i];
6796
- const next = content[i + 1];
6797
- if (family === "py" && (c === "\"" || c === "'")) {
6798
- if (content[i + 1] === c && content[i + 2] === c) {
6799
- const triple = c + c + c;
6800
- const end = content.indexOf(triple, i + 3);
6801
- const stop = end === -1 ? len : end + 3;
6802
- mask(i + 3, stop - 3);
6803
- i = stop;
6804
- continue;
6805
- }
6806
- }
6807
- if (c === "\"" || c === "'") {
6808
- const strStart = i;
6809
- i = consumeQuotedString(content, i, c);
6810
- mask(strStart + 1, i - 1);
6811
- continue;
6812
- }
6813
- if ((family === "py" || family === "rb" || family === "php") && c === "#") {
6814
- const strStart = i;
6815
- while (i < len && content[i] !== "\n") i++;
6816
- mask(strStart, i);
6817
- continue;
6818
- }
6819
- if (family === "php" && c === "/" && next === "/") {
6820
- const strStart = i;
6821
- while (i < len && content[i] !== "\n") i++;
6822
- mask(strStart, i);
6823
- continue;
6824
- }
6825
- if (family === "php" && c === "/" && next === "*") {
6826
- const strStart = i;
6827
- i += 2;
6828
- while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
6829
- if (i < len - 1) i += 2;
6830
- mask(strStart, i);
6831
- continue;
6832
- }
6833
- i++;
6834
- }
6835
- return out.join("");
6836
- };
6837
-
6838
6940
  //#endregion
6839
6941
  //#region src/engines/security/risky.ts
6840
6942
  const ev = "eval";
@@ -7109,6 +7211,7 @@ const scanSecrets = async (context) => {
7109
7211
  } catch {
7110
7212
  continue;
7111
7213
  }
7214
+ content = maskComments(content, path.extname(filePath));
7112
7215
  const relativePath = path.relative(context.rootDirectory, filePath);
7113
7216
  for (const { pattern, name, keywordPrefixed } of SECRET_PATTERNS) {
7114
7217
  const regex = new RegExp(pattern.source, pattern.flags);
@@ -11730,7 +11833,7 @@ const runNpmAuditFix = async (rootDir, onProgress) => {
11730
11833
  });
11731
11834
  if (installResult.exitCode !== 0) throw new Error(installResult.stderr || installResult.stdout || "npm install failed after audit fix");
11732
11835
  };
11733
- const fetchLatestVersion = async (rootDir, pkgName, pm) => {
11836
+ const fetchLatestVersion$1 = async (rootDir, pkgName, pm) => {
11734
11837
  try {
11735
11838
  const result = await runSubprocess(pm, [
11736
11839
  "view",
@@ -11750,7 +11853,7 @@ const collectOverrides = async (rootDir, vulnerabilities, pm) => {
11750
11853
  const overrides = {};
11751
11854
  for (const [pkgName, vuln] of Object.entries(vulnerabilities)) {
11752
11855
  if (vuln.fixAvailable !== false || !vuln.range) continue;
11753
- const latest = await fetchLatestVersion(rootDir, pkgName, pm);
11856
+ const latest = await fetchLatestVersion$1(rootDir, pkgName, pm);
11754
11857
  if (latest) overrides[pkgName] = latest;
11755
11858
  }
11756
11859
  return overrides;
@@ -12713,6 +12816,86 @@ const trendCommand = (directory, limit) => {
12713
12816
  }));
12714
12817
  };
12715
12818
 
12819
+ //#endregion
12820
+ //#region src/update-notifier.ts
12821
+ const REGISTRY_URL = "https://registry.npmjs.org/aislop/latest";
12822
+ const CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
12823
+ const REQUEST_TIMEOUT_MS = 2e3;
12824
+ const CACHE_BASENAME = "update_check.json";
12825
+ const isUpdateNotifierDisabled = (env = process.env) => {
12826
+ if (env.AISLOP_NO_UPDATE_NOTIFIER === "1") return true;
12827
+ if (env.NO_UPDATE_NOTIFIER === "1") return true;
12828
+ if (env.DO_NOT_TRACK === "1") return true;
12829
+ return isCiEnv(env);
12830
+ };
12831
+ const resolveUpdateCachePath = (homedir = os.homedir(), env = process.env) => {
12832
+ if (process.platform === "linux" && env.XDG_STATE_HOME) return path.join(env.XDG_STATE_HOME, "aislop", CACHE_BASENAME);
12833
+ return path.join(homedir, ".aislop", CACHE_BASENAME);
12834
+ };
12835
+ const parseVersion = (raw) => {
12836
+ const m = raw.trim().replace(/^v/, "").split(/[-+]/, 1)[0].match(/^(\d+)\.(\d+)\.(\d+)$/);
12837
+ if (!m) return null;
12838
+ return {
12839
+ major: Number(m[1]),
12840
+ minor: Number(m[2]),
12841
+ patch: Number(m[3])
12842
+ };
12843
+ };
12844
+ const isOutdated = (current, latest) => {
12845
+ const c = parseVersion(current);
12846
+ const l = parseVersion(latest);
12847
+ if (!c || !l) return false;
12848
+ if (l.major !== c.major) return l.major > c.major;
12849
+ if (l.minor !== c.minor) return l.minor > c.minor;
12850
+ return l.patch > c.patch;
12851
+ };
12852
+ const formatUpdateNotice = (current, latest) => `\nUpdate available: ${current} -> ${latest}. Run npx aislop@latest to upgrade.\n`;
12853
+ const readCache = (cachePath) => {
12854
+ try {
12855
+ const parsed = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
12856
+ if (typeof parsed?.latest === "string" && typeof parsed?.checkedAt === "number") return {
12857
+ latest: parsed.latest,
12858
+ checkedAt: parsed.checkedAt
12859
+ };
12860
+ return null;
12861
+ } catch {
12862
+ return null;
12863
+ }
12864
+ };
12865
+ const writeCache = (cachePath, cache) => {
12866
+ try {
12867
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
12868
+ fs.writeFileSync(cachePath, JSON.stringify(cache));
12869
+ return true;
12870
+ } catch {
12871
+ return false;
12872
+ }
12873
+ };
12874
+ const fetchLatestVersion = async () => {
12875
+ try {
12876
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
12877
+ if (!res.ok) return null;
12878
+ const data = await res.json();
12879
+ return typeof data.version === "string" ? data.version : null;
12880
+ } catch {
12881
+ return null;
12882
+ }
12883
+ };
12884
+ const maybeNotifyUpdate = async (now = Date.now()) => {
12885
+ if (isUpdateNotifierDisabled()) return;
12886
+ if (!process.stderr.isTTY) return;
12887
+ const cachePath = resolveUpdateCachePath();
12888
+ const cache = readCache(cachePath);
12889
+ if (cache && isOutdated(APP_VERSION, cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
12890
+ if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
12891
+ const latest = await fetchLatestVersion();
12892
+ if (latest) writeCache(cachePath, {
12893
+ latest,
12894
+ checkedAt: now
12895
+ });
12896
+ }
12897
+ };
12898
+
12716
12899
  //#endregion
12717
12900
  //#region src/cli.ts
12718
12901
  process.on("SIGINT", () => process.exit(0));
@@ -12966,6 +13149,7 @@ const main = async () => {
12966
13149
  fireInstalledOnce();
12967
13150
  await program.parseAsync();
12968
13151
  await flushTelemetry();
13152
+ await maybeNotifyUpdate();
12969
13153
  };
12970
13154
  main();
12971
13155