codeguardian-mcp 1.3.6 → 1.3.8

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.
@@ -19,10 +19,10 @@ import * as path from "path";
19
19
  import * as fsSync from "fs";
20
20
  import { globSync } from "glob";
21
21
  import { resolveImport } from "../../context/projectContext.js";
22
- import { extractSymbolsAST, collectLocalDefinitionsAST } from "./extractors/index.js";
22
+ import { extractSymbolsAST, collectLocalDefinitionsAST, } from "./extractors/index.js";
23
23
  import { parseCodeCached } from "./parser.js";
24
24
  import { suggestSimilar, extractSimilarSymbols } from "./scoring.js";
25
- import { isPythonSymbolExported, getPythonPipNameForImport } from "./manifest.js";
25
+ import { isPythonSymbolExported, getPythonPipNameForImport, } from "./manifest.js";
26
26
  import { isJSBuiltin, isPythonBuiltin, isTSBuiltinType, NODE_BUILTIN_MODULES, } from "./builtins.js";
27
27
  import { usagePatternAnalyzer } from "../../analyzers/usagePatterns.js";
28
28
  import { isContextuallyValid, } from "./contextualNaming.js";
@@ -50,7 +50,12 @@ function findPrismaSchemaPathSync(projectPath) {
50
50
  // (best-effort; guarded by caching so it won't run repeatedly).
51
51
  try {
52
52
  const matches = globSync(path.join(projectPath, "**/prisma/schema.prisma"), {
53
- ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"],
53
+ ignore: [
54
+ "**/node_modules/**",
55
+ "**/dist/**",
56
+ "**/build/**",
57
+ "**/.git/**",
58
+ ],
54
59
  nodir: true,
55
60
  absolute: true,
56
61
  });
@@ -124,8 +129,8 @@ export async function validateManifest(imports, manifest, newCode, language = "t
124
129
  }
125
130
  // Check if package is in manifest
126
131
  if (!manifest.all.has(pkgName)) {
127
- const scopedName = imp.module.startsWith("@") ?
128
- imp.module.split("/").slice(0, 2).join("/")
132
+ const scopedName = imp.module.startsWith("@")
133
+ ? imp.module.split("/").slice(0, 2).join("/")
129
134
  : pkgName;
130
135
  if (!manifest.all.has(scopedName)) {
131
136
  // For Python, check if this import name is a known alias of a pip package
@@ -141,7 +146,7 @@ export async function validateManifest(imports, manifest, newCode, language = "t
141
146
  if (unknownPackages.length === 0)
142
147
  return issues;
143
148
  // Phase 2: Batch registry lookups — deduplicate and check in parallel
144
- const uniquePkgNames = [...new Set(unknownPackages.map(u => u.pkgName))];
149
+ const uniquePkgNames = [...new Set(unknownPackages.map((u) => u.pkgName))];
145
150
  const REGISTRY_BATCH_SIZE = 10;
146
151
  const registryResults = new Map();
147
152
  for (let i = 0; i < uniquePkgNames.length; i += REGISTRY_BATCH_SIZE) {
@@ -487,8 +492,8 @@ typeReferences = []) {
487
492
  for (const sym of newCodeSymbols) {
488
493
  const projectSym = {
489
494
  name: sym.name,
490
- type: sym.type === "interface" || sym.type === "type" ?
491
- "variable"
495
+ type: sym.type === "interface" || sym.type === "type"
496
+ ? "variable"
492
497
  : sym.type,
493
498
  file: "(new code)",
494
499
  params: sym.params,
@@ -606,7 +611,10 @@ typeReferences = []) {
606
611
  file: resolvedFile || imp.module,
607
612
  };
608
613
  validVariables.set(name.local, namespaceSym);
609
- validFunctions.set(name.local, { ...namespaceSym, type: "function" });
614
+ validFunctions.set(name.local, {
615
+ ...namespaceSym,
616
+ type: "function",
617
+ });
610
618
  validClasses.set(name.local, { ...namespaceSym, type: "class" });
611
619
  }
612
620
  continue;
@@ -623,7 +631,8 @@ typeReferences = []) {
623
631
  // Double check against all symbols in file marked as exported
624
632
  // (sometimes exports are implicit in simple extractors)
625
633
  // For Python: ALL module-level names are importable (no export keyword)
626
- let symExport = fileInfo.symbols.find((s) => (language === "python" || s.exported) && s.name === name.imported);
634
+ let symExport = fileInfo.symbols.find((s) => (language === "python" || s.exported) &&
635
+ s.name === name.imported);
627
636
  // For Python: check if the symbol is imported at module level in the target file.
628
637
  // In Python, any name imported at module level is part of the module's namespace
629
638
  // and is importable by other modules (e.g., `from app.api.cli_tokens import CLIToken`
@@ -633,7 +642,12 @@ typeReferences = []) {
633
642
  modImp.defaultImport === name.imported);
634
643
  if (isImportedInModule) {
635
644
  // Treat as found — symbol is a valid Python namespace member via re-import
636
- symExport = { name: name.imported, kind: "variable", line: 0, exported: true };
645
+ symExport = {
646
+ name: name.imported,
647
+ kind: "variable",
648
+ line: 0,
649
+ exported: true,
650
+ };
637
651
  }
638
652
  }
639
653
  if (!symExport) {
@@ -645,7 +659,8 @@ typeReferences = []) {
645
659
  : path.dirname(resolvedFile);
646
660
  const subModPy = path.join(resolvedDir, `${name.imported}.py`);
647
661
  const subModInit = path.join(resolvedDir, name.imported, "__init__.py");
648
- if (context.files.has(subModPy) || context.files.has(subModInit)) {
662
+ if (context.files.has(subModPy) ||
663
+ context.files.has(subModInit)) {
649
664
  // Valid sub-module import
650
665
  continue;
651
666
  }
@@ -759,7 +774,9 @@ typeReferences = []) {
759
774
  if (language === "python" && projectSym) {
760
775
  // First check if it's a sub-module file (sub-modules don't need __all__)
761
776
  if (context) {
762
- const moduleDir = imp.module.replace(/^\.+/, "").replace(/\./g, "/");
777
+ const moduleDir = imp.module
778
+ .replace(/^\.+/, "")
779
+ .replace(/\./g, "/");
763
780
  const basePath = context.projectPath;
764
781
  let subModFound = false;
765
782
  const subModPy = path.join(basePath, moduleDir, `${name.imported}.py`);
@@ -821,7 +838,9 @@ typeReferences = []) {
821
838
  // For Python: check if the imported name is a sub-module file
822
839
  // e.g., "from app.api import auth" where app/api/auth.py exists
823
840
  if (language === "python" && context) {
824
- const modulePath = imp.module.replace(/^\.+/, "").replace(/\./g, "/");
841
+ const modulePath = imp.module
842
+ .replace(/^\.+/, "")
843
+ .replace(/\./g, "/");
825
844
  const basePath = context.projectPath;
826
845
  let resolvedSubMod = null;
827
846
  const subModulePy = path.join(basePath, modulePath, `${name.imported}.py`);
@@ -946,7 +965,8 @@ typeReferences = []) {
946
965
  const fn = valueNode.childForFieldName("function");
947
966
  if (fn?.type === "member_expression") {
948
967
  const prop = fn.childForFieldName("property");
949
- if (prop && newCode.slice(prop.startIndex, prop.endIndex) === "json") {
968
+ if (prop &&
969
+ newCode.slice(prop.startIndex, prop.endIndex) === "json") {
950
970
  return true;
951
971
  }
952
972
  }
@@ -1045,8 +1065,8 @@ typeReferences = []) {
1045
1065
  issues.push({
1046
1066
  type: "nonExistentFunction",
1047
1067
  severity: "critical",
1048
- message: existsInProject ?
1049
- `Function '${used.name}' exists in your project but is not imported in this file`
1068
+ message: existsInProject
1069
+ ? `Function '${used.name}' exists in your project but is not imported in this file`
1050
1070
  : `Function '${used.name}' does not exist in project`,
1051
1071
  line: used.line,
1052
1072
  file: filePath,
@@ -1071,9 +1091,7 @@ typeReferences = []) {
1071
1091
  existsInProject: true,
1072
1092
  strictMode,
1073
1093
  });
1074
- const expectedRange = min === max
1075
- ? `${max}`
1076
- : `${min}-${max}`;
1094
+ const expectedRange = min === max ? `${max}` : `${min}-${max}`;
1077
1095
  issues.push({
1078
1096
  type: "wrongParamCount",
1079
1097
  severity: "high",
@@ -1081,8 +1099,8 @@ typeReferences = []) {
1081
1099
  line: used.line,
1082
1100
  file: filePath,
1083
1101
  code: used.code,
1084
- suggestion: func.params ?
1085
- `Expected params: ${func.params.join(", ")}`
1102
+ suggestion: func.params
1103
+ ? `Expected params: ${func.params.join(", ")}`
1086
1104
  : `Check the function signature in ${func.file}`,
1087
1105
  confidence,
1088
1106
  reasoning,
@@ -1108,7 +1126,7 @@ typeReferences = []) {
1108
1126
  // Example: `${sop.code}-${sop.number}`.toLowerCase().includes(q)
1109
1127
  // Returning a pseudo-root like `${sop` causes false undefinedVariable findings.
1110
1128
  if (trimmedObj.startsWith("`") ||
1111
- trimmedObj.startsWith("\"") ||
1129
+ trimmedObj.startsWith('"') ||
1112
1130
  trimmedObj.startsWith("'")) {
1113
1131
  return "";
1114
1132
  }
@@ -1139,7 +1157,9 @@ typeReferences = []) {
1139
1157
  // Not a type assertion — check if it's an arithmetic/logical/nullish expression
1140
1158
  // e.g., (i + 1), (value / 1000), (milestones || []), (budgetUsedPercentage || 0)
1141
1159
  // These are NOT object references — skip method validation entirely.
1142
- if (/[+\-*/%|&<>=!~^]/.test(innerExpr) || /\s\|\|\s/.test(innerExpr) || /\s\?\?\s/.test(innerExpr)) {
1160
+ if (/[+\-*/%|&<>=!~^]/.test(innerExpr) ||
1161
+ /\s\|\|\s/.test(innerExpr) ||
1162
+ /\s\?\?\s/.test(innerExpr)) {
1143
1163
  return ""; // Not a valid object reference
1144
1164
  }
1145
1165
  // Otherwise extract from the inner expression (e.g., (myObj).method())
@@ -1177,15 +1197,20 @@ typeReferences = []) {
1177
1197
  if (!first || first.toUpperCase() === first)
1178
1198
  return "";
1179
1199
  const candidate = first.toUpperCase() + rootObject.slice(1);
1180
- return projectClasses.has(candidate) || hasContextClass(candidate) ? candidate : "";
1200
+ return projectClasses.has(candidate) || hasContextClass(candidate)
1201
+ ? candidate
1202
+ : "";
1181
1203
  })();
1182
1204
  const scopesToMatch = new Set([used.object, rootObject, inferredClassName].filter(Boolean));
1183
1205
  // CRITICAL FIX: Skip ALL 'this'/'self'/'cls' method calls - we can't validate class scope
1184
1206
  // The 'this' keyword (JS/TS) and 'self'/'cls' (Python) are always in scope within a class context
1185
1207
  // TypeScript/ESLint/mypy handle class method validation, not CodeGuardian
1186
- if (used.object === "this" || used.object?.startsWith("this.") ||
1187
- used.object === "self" || used.object?.startsWith("self.") ||
1188
- used.object === "cls" || used.object?.startsWith("cls.")) {
1208
+ if (used.object === "this" ||
1209
+ used.object?.startsWith("this.") ||
1210
+ used.object === "self" ||
1211
+ used.object?.startsWith("self.") ||
1212
+ used.object === "cls" ||
1213
+ used.object?.startsWith("cls.")) {
1189
1214
  continue; // Trust class scope - this is not a hallucination risk
1190
1215
  }
1191
1216
  // Skip other whitelisted global objects and common patterns
@@ -1315,9 +1340,7 @@ typeReferences = []) {
1315
1340
  // that `<model>` exists as a Prisma delegate derived from schema.prisma.
1316
1341
  // This catches hallucinations like `prisma.ghostItems.findMany()`.
1317
1342
  if (rootObject === "prisma" && typeof used.object === "string") {
1318
- const expr = used.object
1319
- .replace(/\s+/g, "")
1320
- .replace(/\?\./g, ".");
1343
+ const expr = used.object.replace(/\s+/g, "").replace(/\?\./g, ".");
1321
1344
  // Only handle the delegate form. Client-level methods like `prisma.$transaction()`
1322
1345
  // have used.object === "prisma" and should not be checked here.
1323
1346
  const parts = expr.split(".").filter(Boolean);
@@ -1373,9 +1396,18 @@ typeReferences = []) {
1373
1396
  // NOT for generic whitelisted objects (db, prisma, etc.) where `as any` may
1374
1397
  // be a legitimate workaround for missing type definitions.
1375
1398
  const STEALTH_HALLUCINATION_GLOBALS = new Set([
1376
- "window", "navigator", "document", "location", "history",
1377
- "localStorage", "sessionStorage", "console", "process",
1378
- "global", "globalThis", "Intl",
1399
+ "window",
1400
+ "navigator",
1401
+ "document",
1402
+ "location",
1403
+ "history",
1404
+ "localStorage",
1405
+ "sessionStorage",
1406
+ "console",
1407
+ "process",
1408
+ "global",
1409
+ "globalThis",
1410
+ "Intl",
1379
1411
  ]);
1380
1412
  if (STEALTH_HALLUCINATION_GLOBALS.has(rootObject)) {
1381
1413
  const codeLine = used.code || "";
@@ -1403,7 +1435,8 @@ typeReferences = []) {
1403
1435
  // Check for @ts-ignore or @ts-expect-error which indicates intentional bypass
1404
1436
  const codeLines = newCode.split("\n");
1405
1437
  const prevLine = codeLines[used.line - 2]; // line is 1-indexed
1406
- if (prevLine?.includes("@ts-ignore") || prevLine?.includes("@ts-expect-error")) {
1438
+ if (prevLine?.includes("@ts-ignore") ||
1439
+ prevLine?.includes("@ts-expect-error")) {
1407
1440
  issues.push({
1408
1441
  type: "nonExistentMethod",
1409
1442
  severity: "high",
@@ -1422,17 +1455,42 @@ typeReferences = []) {
1422
1455
  // - prisma.pantryItem.create() - 'pantryItem' is a model name
1423
1456
  // We should validate that the METHOD (findMany, create, etc.) is a known ORM method
1424
1457
  // and flag hallucinated methods like hallucinateMissingFunction()
1425
- const ORM_OBJECTS = new Set(["prisma", "db", "entityManager", "repository", "queryBuilder"]);
1458
+ const ORM_OBJECTS = new Set([
1459
+ "prisma",
1460
+ "db",
1461
+ "entityManager",
1462
+ "repository",
1463
+ "queryBuilder",
1464
+ ]);
1426
1465
  if (ORM_OBJECTS.has(rootObject)) {
1427
1466
  // Known Prisma methods
1428
1467
  const KNOWN_PRISMA_METHODS = new Set([
1429
1468
  // Model-level methods (called on prisma.model)
1430
- "findMany", "findUnique", "findFirst", "findFirstOrThrow", "findUniqueOrThrow",
1431
- "create", "createMany", "update", "updateMany", "upsert",
1432
- "delete", "deleteMany", "count", "aggregate", "groupBy",
1469
+ "findMany",
1470
+ "findUnique",
1471
+ "findFirst",
1472
+ "findFirstOrThrow",
1473
+ "findUniqueOrThrow",
1474
+ "create",
1475
+ "createMany",
1476
+ "update",
1477
+ "updateMany",
1478
+ "upsert",
1479
+ "delete",
1480
+ "deleteMany",
1481
+ "count",
1482
+ "aggregate",
1483
+ "groupBy",
1433
1484
  // Client-level methods (called on prisma)
1434
- "$connect", "$disconnect", "$transaction", "$queryRaw", "$executeRaw",
1435
- "$use", "$on", "$extends", "$extends",
1485
+ "$connect",
1486
+ "$disconnect",
1487
+ "$transaction",
1488
+ "$queryRaw",
1489
+ "$executeRaw",
1490
+ "$use",
1491
+ "$on",
1492
+ "$extends",
1493
+ "$extends",
1436
1494
  ]);
1437
1495
  // Check if this is a model-level call (prisma.modelName.method())
1438
1496
  // or a client-level call (prisma.$method())
@@ -1443,7 +1501,8 @@ typeReferences = []) {
1443
1501
  // Check for @ts-ignore or @ts-expect-error which indicates intentional bypass
1444
1502
  const codeLines = newCode.split("\n");
1445
1503
  const prevLine = codeLines[used.line - 2]; // line is 1-indexed
1446
- if (prevLine?.includes("@ts-ignore") || prevLine?.includes("@ts-expect-error")) {
1504
+ if (prevLine?.includes("@ts-ignore") ||
1505
+ prevLine?.includes("@ts-expect-error")) {
1447
1506
  issues.push({
1448
1507
  type: "nonExistentMethod",
1449
1508
  severity: "high",
@@ -1482,7 +1541,8 @@ typeReferences = []) {
1482
1541
  // Check if the object is a locally-defined variable (function params, assignments, loop vars, etc.)
1483
1542
  localDefinitions.has(rootObject) ||
1484
1543
  // Python: Check if rootObject is the base of a dotted import (e.g., `import concurrent.futures` → `concurrent`)
1485
- (language === "python" && imports.some(imp => imp.names.some(n => n.local.startsWith(rootObject + ".")))) ||
1544
+ (language === "python" &&
1545
+ imports.some((imp) => imp.names.some((n) => n.local.startsWith(rootObject + ".")))) ||
1486
1546
  // Always trust common short variable names in non-strict mode
1487
1547
  (!strictMode &&
1488
1548
  [
@@ -1630,7 +1690,8 @@ typeReferences = []) {
1630
1690
  line: used.line,
1631
1691
  file: filePath,
1632
1692
  code: used.code,
1633
- suggestion: suggestion || "Use a real React API (e.g., useState, useEffect, memo, forwardRef).",
1693
+ suggestion: suggestion ||
1694
+ "Use a real React API (e.g., useState, useEffect, memo, forwardRef).",
1634
1695
  confidence: 86,
1635
1696
  reasoning: `The object '${objectName}' is imported from 'react'. React's public API is stable; '${used.name}' is not in a conservative allowlist of common React namespace methods/hooks.`,
1636
1697
  });
@@ -1647,7 +1708,9 @@ typeReferences = []) {
1647
1708
  else if (!imp.isExternal) {
1648
1709
  // For internal imports, check if we have CLASS info
1649
1710
  const objClass = projectClasses.get(objectName) ||
1650
- (inferredClassName ? projectClasses.get(inferredClassName) : undefined);
1711
+ (inferredClassName
1712
+ ? projectClasses.get(inferredClassName)
1713
+ : undefined);
1651
1714
  if (objClass || inferredClassName) {
1652
1715
  shouldCheck = true; // We have class info, so we can validate methods
1653
1716
  }
@@ -1683,8 +1746,11 @@ typeReferences = []) {
1683
1746
  // ONLY check if we have class info, otherwise we don't know the type enough to flag it
1684
1747
  // Use projectClasses (actual class definitions), not validClasses (which includes all imports)
1685
1748
  const objClass = projectClasses.get(objectName) ||
1686
- (inferredClassName ? projectClasses.get(inferredClassName) : undefined);
1687
- if ((objClass || inferredClassName) && objClass?.file !== "(new code)") {
1749
+ (inferredClassName
1750
+ ? projectClasses.get(inferredClassName)
1751
+ : undefined);
1752
+ if ((objClass || inferredClassName) &&
1753
+ objClass?.file !== "(new code)") {
1688
1754
  shouldCheck = true;
1689
1755
  }
1690
1756
  // Local literal inference: method calls on obvious built-in types (e.g., array literals)
@@ -1731,30 +1797,260 @@ typeReferences = []) {
1731
1797
  // and won't appear in the project symbol table
1732
1798
  const FRAMEWORK_METHODS = new Set([
1733
1799
  // Pydantic BaseModel methods (v1 + v2)
1734
- "model_validate", "model_dump", "model_json_schema", "model_copy",
1735
- "model_validate_json", "model_dump_json", "model_fields_set",
1736
- "model_construct", "model_post_init", "model_rebuild",
1737
- "dict", "json", "parse_obj", "parse_raw", "parse_file",
1738
- "from_orm", "schema", "schema_json", "validate", "update_forward_refs",
1739
- "copy", "construct",
1740
- // SQLAlchemy Model/Query methods
1741
- "query", "filter", "filter_by", "all", "first", "one", "one_or_none",
1742
- "get", "count", "delete", "update", "order_by", "limit", "offset",
1743
- "join", "outerjoin", "group_by", "having", "distinct", "subquery",
1744
- "scalar", "scalars", "execute", "add", "flush", "commit", "rollback",
1745
- "refresh", "expire", "expunge", "merge", "close",
1800
+ "model_validate",
1801
+ "model_dump",
1802
+ "model_json_schema",
1803
+ "model_copy",
1804
+ "model_validate_json",
1805
+ "model_dump_json",
1806
+ "model_fields_set",
1807
+ "model_construct",
1808
+ "model_post_init",
1809
+ "model_rebuild",
1810
+ "dict",
1811
+ "json",
1812
+ "parse_obj",
1813
+ "parse_raw",
1814
+ "parse_file",
1815
+ "from_orm",
1816
+ "schema",
1817
+ "schema_json",
1818
+ "validate",
1819
+ "update_forward_refs",
1820
+ "copy",
1821
+ "construct",
1822
+ // SQLAlchemy Model/Query methods (legacy 1.x style)
1823
+ "query",
1824
+ "filter",
1825
+ "filter_by",
1826
+ "all",
1827
+ "first",
1828
+ "one",
1829
+ "one_or_none",
1830
+ "get",
1831
+ "count",
1832
+ "delete",
1833
+ "update",
1834
+ "order_by",
1835
+ "limit",
1836
+ "offset",
1837
+ "join",
1838
+ "outerjoin",
1839
+ "group_by",
1840
+ "having",
1841
+ "distinct",
1842
+ "subquery",
1843
+ "scalar",
1844
+ "scalars",
1845
+ "execute",
1846
+ "add",
1847
+ "flush",
1848
+ "commit",
1849
+ "rollback",
1850
+ "refresh",
1851
+ "expire",
1852
+ "expunge",
1853
+ "merge",
1854
+ "close",
1855
+ // SQLAlchemy 2.0 Core / Select / Result methods
1856
+ // These are called on select(...) / session.execute(...) results and are
1857
+ // extremely common in async FastAPI/SQLAlchemy 2.0 codebases.
1858
+ "where",
1859
+ "select_from",
1860
+ "scalar_one_or_none",
1861
+ "scalar_one",
1862
+ "fetchall",
1863
+ "fetchone",
1864
+ "fetchmany",
1865
+ "mappings",
1866
+ "partitions",
1867
+ "unique",
1868
+ "tuples",
1869
+ "columns",
1870
+ "returning",
1871
+ "with_for_update",
1872
+ "correlate",
1873
+ "correlate_except",
1874
+ "execution_options",
1875
+ "options",
1876
+ "populate_existing",
1877
+ "yield_per",
1878
+ // SQLAlchemy event system (e.g., @event.listens_for(Model, 'after_insert'))
1879
+ "listens_for",
1880
+ "listen",
1881
+ "remove",
1746
1882
  // SQLAlchemy Column expression methods (called on Model.column attributes)
1747
- "desc", "asc", "in_", "notin_", "not_in", "isnot", "is_", "is_not",
1748
- "like", "ilike", "not_like", "not_ilike", "contains", "startswith", "endswith",
1749
- "between", "any_", "has", "label", "cast", "op", "collate",
1750
- "nullsfirst", "nullslast", "regexp_match", "regexp_replace",
1751
- "concat", "distinct", "nulls_first", "nulls_last",
1883
+ "desc",
1884
+ "asc",
1885
+ "in_",
1886
+ "notin_",
1887
+ "not_in",
1888
+ "isnot",
1889
+ "is_",
1890
+ "is_not",
1891
+ "like",
1892
+ "ilike",
1893
+ "not_like",
1894
+ "not_ilike",
1895
+ "contains",
1896
+ "startswith",
1897
+ "endswith",
1898
+ "between",
1899
+ "any_",
1900
+ "has",
1901
+ "label",
1902
+ "cast",
1903
+ "op",
1904
+ "collate",
1905
+ "nullsfirst",
1906
+ "nullslast",
1907
+ "regexp_match",
1908
+ "regexp_replace",
1909
+ "concat",
1910
+ "nulls_first",
1911
+ "nulls_last",
1752
1912
  // Django ORM methods
1753
- "objects", "create", "get_or_create", "update_or_create",
1754
- "bulk_create", "bulk_update", "values", "values_list",
1755
- "annotate", "aggregate", "exists", "exclude", "select_related",
1756
- "prefetch_related", "defer", "only", "using", "raw",
1757
- "save", "full_clean", "clean", "clean_fields",
1913
+ "objects",
1914
+ "create",
1915
+ "get_or_create",
1916
+ "update_or_create",
1917
+ "bulk_create",
1918
+ "bulk_update",
1919
+ "values",
1920
+ "values_list",
1921
+ "annotate",
1922
+ "aggregate",
1923
+ "exists",
1924
+ "exclude",
1925
+ "select_related",
1926
+ "prefetch_related",
1927
+ "defer",
1928
+ "only",
1929
+ "using",
1930
+ "raw",
1931
+ "save",
1932
+ "full_clean",
1933
+ "clean",
1934
+ "clean_fields",
1935
+ // Python stdlib – datetime / date / time
1936
+ // These are classmethods or instance methods on datetime/date/timedelta objects.
1937
+ // They are imported directly (from datetime import datetime) and their
1938
+ // type is not in the project symbol table, so the method check fires falsely.
1939
+ "now",
1940
+ "utcnow",
1941
+ "today",
1942
+ "fromisoformat",
1943
+ "fromtimestamp",
1944
+ "utcfromtimestamp",
1945
+ "fromordinal",
1946
+ "combine",
1947
+ "strptime",
1948
+ "strftime",
1949
+ "isoformat",
1950
+ "timetuple",
1951
+ "toordinal",
1952
+ "weekday",
1953
+ "isoweekday",
1954
+ "isocalendar",
1955
+ "ctime",
1956
+ "timestamp",
1957
+ "utctimetuple",
1958
+ "astimezone",
1959
+ "dst",
1960
+ "tzname",
1961
+ "utcoffset",
1962
+ // Python stdlib – timedelta
1963
+ "total_seconds",
1964
+ // Python stdlib – pathlib.Path / os.path operations
1965
+ "resolve",
1966
+ "absolute",
1967
+ "expanduser",
1968
+ "iterdir",
1969
+ "glob",
1970
+ "rglob",
1971
+ "mkdir",
1972
+ "rmdir",
1973
+ "unlink",
1974
+ "rename",
1975
+ "stat",
1976
+ "lstat",
1977
+ "open",
1978
+ "read_text",
1979
+ "write_text",
1980
+ "read_bytes",
1981
+ "write_bytes",
1982
+ "is_file",
1983
+ "is_dir",
1984
+ "is_symlink",
1985
+ "exists",
1986
+ "samefile",
1987
+ "with_name",
1988
+ "with_suffix",
1989
+ "with_stem",
1990
+ "relative_to",
1991
+ // Python stdlib – itertools / functools
1992
+ "group", // itertools.groupby group object
1993
+ "chain",
1994
+ // Jinja2 Template rendering
1995
+ "render",
1996
+ "render_async",
1997
+ "generate",
1998
+ "stream",
1999
+ "make_module",
2000
+ "get_template",
2001
+ "select_template",
2002
+ "get_or_select_template",
2003
+ // Prometheus / metrics clients (prometheus-client, opentelemetry-sdk)
2004
+ "inc",
2005
+ "dec",
2006
+ "observe",
2007
+ "set_to_current_time",
2008
+ "labels",
2009
+ "remove",
2010
+ // OpenTelemetry instrumentation methods
2011
+ "instrument",
2012
+ "uninstrument",
2013
+ "start_span",
2014
+ "use_span",
2015
+ "set_attribute",
2016
+ "add_event",
2017
+ "record_exception",
2018
+ "set_status",
2019
+ // Cloudinary / file storage SDK methods
2020
+ "upload",
2021
+ "destroy",
2022
+ "rename",
2023
+ "explicit",
2024
+ "generate_url",
2025
+ "add_tag",
2026
+ "remove_tag",
2027
+ "replace_tag",
2028
+ "remove_all_tags",
2029
+ // NATS / message broker methods
2030
+ "connect",
2031
+ "subscribe",
2032
+ "publish",
2033
+ "unsubscribe",
2034
+ "drain",
2035
+ "flush",
2036
+ // Alembic migration helpers
2037
+ "op",
2038
+ "batch_alter_table",
2039
+ "add_column",
2040
+ "drop_column",
2041
+ "create_table",
2042
+ "drop_table",
2043
+ "create_index",
2044
+ "drop_index",
2045
+ "alter_column",
2046
+ "bulk_insert",
2047
+ // Authlib / OAuth methods
2048
+ "authorize_redirect",
2049
+ "authorize_access_token",
2050
+ "parse_id_token",
2051
+ "userinfo",
2052
+ "revoke_token",
2053
+ "introspect_token",
1758
2054
  ]);
1759
2055
  if (FRAMEWORK_METHODS.has(used.name))
1760
2056
  continue;
@@ -1766,25 +2062,80 @@ typeReferences = []) {
1766
2062
  if (language === "python") {
1767
2063
  const PYTHON_DYNAMIC_METHODS = new Set([
1768
2064
  // HTTP client methods (httpx, requests, aiohttp, etc.)
1769
- "post", "put", "patch", "request", "head", "options",
2065
+ "post",
2066
+ "put",
2067
+ "patch",
2068
+ "request",
2069
+ "head",
2070
+ "options",
1770
2071
  // Python string methods called on model attribute chains (e.g., user.email.split())
1771
- "split", "strip", "lstrip", "rstrip", "lower", "upper",
1772
- "replace", "encode", "decode", "format", "capitalize",
2072
+ "split",
2073
+ "strip",
2074
+ "lstrip",
2075
+ "rstrip",
2076
+ "lower",
2077
+ "upper",
2078
+ "replace",
2079
+ "encode",
2080
+ "decode",
2081
+ "format",
2082
+ "capitalize",
1773
2083
  // Storage/external client methods (Supabase, S3, GCS, etc.)
1774
- "from_", "upload", "download", "create_signed_url", "get_public_url",
1775
- "get_bucket", "create_bucket", "remove", "list",
2084
+ "from_",
2085
+ "upload",
2086
+ "download",
2087
+ "create_signed_url",
2088
+ "get_public_url",
2089
+ "get_bucket",
2090
+ "create_bucket",
2091
+ "remove",
2092
+ "list",
1776
2093
  // Redis client methods
1777
- "setex", "setnx", "getex", "hset", "hget", "hdel", "hgetall",
1778
- "lpush", "rpush", "lpop", "rpop", "lrange", "sadd", "srem", "smembers",
1779
- "zadd", "zrem", "zrange", "zrangebyscore", "expire", "ttl", "pttl",
1780
- "publish", "subscribe", "unsubscribe", "pipeline",
2094
+ "setex",
2095
+ "setnx",
2096
+ "getex",
2097
+ "hset",
2098
+ "hget",
2099
+ "hdel",
2100
+ "hgetall",
2101
+ "lpush",
2102
+ "rpush",
2103
+ "lpop",
2104
+ "rpop",
2105
+ "lrange",
2106
+ "sadd",
2107
+ "srem",
2108
+ "smembers",
2109
+ "zadd",
2110
+ "zrem",
2111
+ "zrange",
2112
+ "zrangebyscore",
2113
+ "expire",
2114
+ "ttl",
2115
+ "pttl",
2116
+ "publish",
2117
+ "subscribe",
2118
+ "unsubscribe",
2119
+ "pipeline",
1781
2120
  // Webhook/integration client methods
1782
- "create_webhook", "delete_webhook", "grant_access", "revoke_access",
1783
- "get_commit_details", "get_commits", "get_branches",
2121
+ "create_webhook",
2122
+ "delete_webhook",
2123
+ "grant_access",
2124
+ "revoke_access",
2125
+ "get_commit_details",
2126
+ "get_commits",
2127
+ "get_branches",
1784
2128
  // Task/job object attribute access
1785
- "func", "args", "kwargs", "result", "status",
2129
+ "func",
2130
+ "args",
2131
+ "kwargs",
2132
+ "result",
2133
+ "status",
1786
2134
  // Celery task methods
1787
- "delay", "apply_async", "retry", "revoke",
2135
+ "delay",
2136
+ "apply_async",
2137
+ "retry",
2138
+ "revoke",
1788
2139
  ]);
1789
2140
  if (PYTHON_DYNAMIC_METHODS.has(used.name))
1790
2141
  continue;
@@ -1866,8 +2217,8 @@ typeReferences = []) {
1866
2217
  issues.push({
1867
2218
  type: "nonExistentClass",
1868
2219
  severity: "critical",
1869
- message: existsInProject ?
1870
- `Class '${used.name}' exists in your project but is not imported in this file`
2220
+ message: existsInProject
2221
+ ? `Class '${used.name}' exists in your project but is not imported in this file`
1871
2222
  : `Class '${used.name}' does not exist in project`,
1872
2223
  line: used.line,
1873
2224
  file: filePath,
@@ -1881,9 +2232,12 @@ typeReferences = []) {
1881
2232
  else if (used.type === "reference") {
1882
2233
  // CRITICAL FIX: Skip property access on 'this'/'self'/'cls' (e.g., this.ws, self.data, cls._client)
1883
2234
  // These are class properties and should not be validated as standalone variables
1884
- if (used.object === "this" || used.object?.startsWith("this.") ||
1885
- used.object === "self" || used.object?.startsWith("self.") ||
1886
- used.object === "cls" || used.object?.startsWith("cls.")) {
2235
+ if (used.object === "this" ||
2236
+ used.object?.startsWith("this.") ||
2237
+ used.object === "self" ||
2238
+ used.object?.startsWith("self.") ||
2239
+ used.object === "cls" ||
2240
+ used.object?.startsWith("cls.")) {
1887
2241
  continue; // Trust class scope - properties are validated by TypeScript/mypy
1888
2242
  }
1889
2243
  const func = validFunctions.get(used.name);
@@ -1965,8 +2319,8 @@ typeReferences = []) {
1965
2319
  issues.push({
1966
2320
  type: "undefinedVariable",
1967
2321
  severity: "critical",
1968
- message: existsInProject ?
1969
- `Variable '${used.name}' exists in your project but is not imported in this file`
2322
+ message: existsInProject
2323
+ ? `Variable '${used.name}' exists in your project but is not imported in this file`
1970
2324
  : `Variable '${used.name}' is not defined or imported`,
1971
2325
  line: used.line,
1972
2326
  file: filePath,
@@ -1996,8 +2350,14 @@ typeReferences = []) {
1996
2350
  let currentModuleAllExports = null;
1997
2351
  if (language === "python" && filePath && pythonExports.size > 0) {
1998
2352
  const basePath = context?.projectPath || "";
1999
- const relPath = filePath.startsWith(basePath) ? filePath.slice(basePath.length + 1) : filePath;
2000
- const pyModPath = relPath.replace(/__init__\.py$/, "").replace(/\.py$/, "").replace(/\//g, ".").replace(/\.$/, "");
2353
+ const relPath = filePath.startsWith(basePath)
2354
+ ? filePath.slice(basePath.length + 1)
2355
+ : filePath;
2356
+ const pyModPath = relPath
2357
+ .replace(/__init__\.py$/, "")
2358
+ .replace(/\.py$/, "")
2359
+ .replace(/\//g, ".")
2360
+ .replace(/\.$/, "");
2001
2361
  currentModuleAllExports = pythonExports.get(pyModPath) || null;
2002
2362
  }
2003
2363
  // For Python: Build a set of symbol names that appear in the code text as word boundaries
@@ -2083,10 +2443,10 @@ typeReferences = []) {
2083
2443
  continue; // Only check type-only imports
2084
2444
  for (const name of imp.names) {
2085
2445
  // Check if this type-imported symbol is used as a value (not just in type positions)
2086
- const usages = usedSymbols.filter(u => u.name === name.local);
2087
- const typeUsages = typeReferences.filter(t => t.name === name.local);
2446
+ const usages = usedSymbols.filter((u) => u.name === name.local);
2447
+ const typeUsages = typeReferences.filter((t) => t.name === name.local);
2088
2448
  // If used in runtime contexts (call, instantiation, etc.) but imported as type
2089
- const runtimeUsages = usages.filter(u => u.type === "call" ||
2449
+ const runtimeUsages = usages.filter((u) => u.type === "call" ||
2090
2450
  u.type === "instantiation" ||
2091
2451
  u.type === "methodCall");
2092
2452
  if (runtimeUsages.length > 0) {