codeguardian-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/dist/agent/agentTools.d.ts +26 -0
  4. package/dist/agent/agentTools.d.ts.map +1 -0
  5. package/dist/agent/agentTools.js +699 -0
  6. package/dist/agent/agentTools.js.map +1 -0
  7. package/dist/agent/autoValidator.d.ts +110 -0
  8. package/dist/agent/autoValidator.d.ts.map +1 -0
  9. package/dist/agent/autoValidator.js +964 -0
  10. package/dist/agent/autoValidator.js.map +1 -0
  11. package/dist/agent/fileWatcher.d.ts +28 -0
  12. package/dist/agent/fileWatcher.d.ts.map +1 -0
  13. package/dist/agent/fileWatcher.js +88 -0
  14. package/dist/agent/fileWatcher.js.map +1 -0
  15. package/dist/agent/guardianPersistence.d.ts +98 -0
  16. package/dist/agent/guardianPersistence.d.ts.map +1 -0
  17. package/dist/agent/guardianPersistence.js +296 -0
  18. package/dist/agent/guardianPersistence.js.map +1 -0
  19. package/dist/agent/mcpNotifications.d.ts +38 -0
  20. package/dist/agent/mcpNotifications.d.ts.map +1 -0
  21. package/dist/agent/mcpNotifications.js +81 -0
  22. package/dist/agent/mcpNotifications.js.map +1 -0
  23. package/dist/analyzers/aiPatterns.d.ts +16 -0
  24. package/dist/analyzers/aiPatterns.d.ts.map +1 -0
  25. package/dist/analyzers/aiPatterns.js +103 -0
  26. package/dist/analyzers/aiPatterns.js.map +1 -0
  27. package/dist/analyzers/antiPatterns.d.ts +60 -0
  28. package/dist/analyzers/antiPatterns.d.ts.map +1 -0
  29. package/dist/analyzers/antiPatterns.js +198 -0
  30. package/dist/analyzers/antiPatterns.js.map +1 -0
  31. package/dist/analyzers/builtinTypes.d.ts +18 -0
  32. package/dist/analyzers/builtinTypes.d.ts.map +1 -0
  33. package/dist/analyzers/builtinTypes.js +1275 -0
  34. package/dist/analyzers/builtinTypes.js.map +1 -0
  35. package/dist/analyzers/complexity.d.ts +14 -0
  36. package/dist/analyzers/complexity.d.ts.map +1 -0
  37. package/dist/analyzers/complexity.js +610 -0
  38. package/dist/analyzers/complexity.js.map +1 -0
  39. package/dist/analyzers/findingVerifier.d.ts +59 -0
  40. package/dist/analyzers/findingVerifier.d.ts.map +1 -0
  41. package/dist/analyzers/findingVerifier.js +1169 -0
  42. package/dist/analyzers/findingVerifier.js.map +1 -0
  43. package/dist/analyzers/impactAnalyzer.d.ts +53 -0
  44. package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
  45. package/dist/analyzers/impactAnalyzer.js +152 -0
  46. package/dist/analyzers/impactAnalyzer.js.map +1 -0
  47. package/dist/analyzers/languageDetector.d.ts +48 -0
  48. package/dist/analyzers/languageDetector.d.ts.map +1 -0
  49. package/dist/analyzers/languageDetector.js +404 -0
  50. package/dist/analyzers/languageDetector.js.map +1 -0
  51. package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
  52. package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
  53. package/dist/analyzers/parsers/incrementalParser.js +193 -0
  54. package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
  55. package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
  56. package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
  57. package/dist/analyzers/parsers/scopeResolver.js +324 -0
  58. package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
  59. package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
  60. package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
  61. package/dist/analyzers/parsers/semanticIndex.js +429 -0
  62. package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
  63. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
  64. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
  65. package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
  66. package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
  67. package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
  68. package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
  69. package/dist/analyzers/parsers/treeSitterParser.js +709 -0
  70. package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
  71. package/dist/analyzers/relevanceScorer.d.ts +43 -0
  72. package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
  73. package/dist/analyzers/relevanceScorer.js +200 -0
  74. package/dist/analyzers/relevanceScorer.js.map +1 -0
  75. package/dist/analyzers/standardLibrary.d.ts +22 -0
  76. package/dist/analyzers/standardLibrary.d.ts.map +1 -0
  77. package/dist/analyzers/standardLibrary.js +211 -0
  78. package/dist/analyzers/standardLibrary.js.map +1 -0
  79. package/dist/analyzers/symbolGraph.d.ts +30 -0
  80. package/dist/analyzers/symbolGraph.d.ts.map +1 -0
  81. package/dist/analyzers/symbolGraph.js +380 -0
  82. package/dist/analyzers/symbolGraph.js.map +1 -0
  83. package/dist/analyzers/symbolTable.d.ts +18 -0
  84. package/dist/analyzers/symbolTable.d.ts.map +1 -0
  85. package/dist/analyzers/symbolTable.js +176 -0
  86. package/dist/analyzers/symbolTable.js.map +1 -0
  87. package/dist/analyzers/typeChecker.d.ts +13 -0
  88. package/dist/analyzers/typeChecker.d.ts.map +1 -0
  89. package/dist/analyzers/typeChecker.js +580 -0
  90. package/dist/analyzers/typeChecker.js.map +1 -0
  91. package/dist/analyzers/usagePatterns.d.ts +42 -0
  92. package/dist/analyzers/usagePatterns.d.ts.map +1 -0
  93. package/dist/analyzers/usagePatterns.js +75 -0
  94. package/dist/analyzers/usagePatterns.js.map +1 -0
  95. package/dist/api-contract/context/backend.d.ts +19 -0
  96. package/dist/api-contract/context/backend.d.ts.map +1 -0
  97. package/dist/api-contract/context/backend.js +64 -0
  98. package/dist/api-contract/context/backend.js.map +1 -0
  99. package/dist/api-contract/context/contract.d.ts +34 -0
  100. package/dist/api-contract/context/contract.d.ts.map +1 -0
  101. package/dist/api-contract/context/contract.js +306 -0
  102. package/dist/api-contract/context/contract.js.map +1 -0
  103. package/dist/api-contract/context/frontend.d.ts +19 -0
  104. package/dist/api-contract/context/frontend.d.ts.map +1 -0
  105. package/dist/api-contract/context/frontend.js +64 -0
  106. package/dist/api-contract/context/frontend.js.map +1 -0
  107. package/dist/api-contract/detector.d.ts +28 -0
  108. package/dist/api-contract/detector.d.ts.map +1 -0
  109. package/dist/api-contract/detector.js +393 -0
  110. package/dist/api-contract/detector.js.map +1 -0
  111. package/dist/api-contract/extractors/python.d.ts +32 -0
  112. package/dist/api-contract/extractors/python.d.ts.map +1 -0
  113. package/dist/api-contract/extractors/python.js +521 -0
  114. package/dist/api-contract/extractors/python.js.map +1 -0
  115. package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
  116. package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
  117. package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
  118. package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
  119. package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
  120. package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
  121. package/dist/api-contract/extractors/tsAstUtils.js +173 -0
  122. package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
  123. package/dist/api-contract/extractors/typescript.d.ts +32 -0
  124. package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
  125. package/dist/api-contract/extractors/typescript.js +666 -0
  126. package/dist/api-contract/extractors/typescript.js.map +1 -0
  127. package/dist/api-contract/index.d.ts +104 -0
  128. package/dist/api-contract/index.d.ts.map +1 -0
  129. package/dist/api-contract/index.js +232 -0
  130. package/dist/api-contract/index.js.map +1 -0
  131. package/dist/api-contract/types.d.ts +151 -0
  132. package/dist/api-contract/types.d.ts.map +1 -0
  133. package/dist/api-contract/types.js +19 -0
  134. package/dist/api-contract/types.js.map +1 -0
  135. package/dist/api-contract/validators/endpoint.d.ts +21 -0
  136. package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
  137. package/dist/api-contract/validators/endpoint.js +224 -0
  138. package/dist/api-contract/validators/endpoint.js.map +1 -0
  139. package/dist/api-contract/validators/index.d.ts +40 -0
  140. package/dist/api-contract/validators/index.d.ts.map +1 -0
  141. package/dist/api-contract/validators/index.js +875 -0
  142. package/dist/api-contract/validators/index.js.map +1 -0
  143. package/dist/api-contract/validators/parameter.d.ts +17 -0
  144. package/dist/api-contract/validators/parameter.d.ts.map +1 -0
  145. package/dist/api-contract/validators/parameter.js +250 -0
  146. package/dist/api-contract/validators/parameter.js.map +1 -0
  147. package/dist/api-contract/validators/type.d.ts +38 -0
  148. package/dist/api-contract/validators/type.d.ts.map +1 -0
  149. package/dist/api-contract/validators/type.js +244 -0
  150. package/dist/api-contract/validators/type.js.map +1 -0
  151. package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
  152. package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
  153. package/dist/context/apiContract/complexTypeSupport.js +665 -0
  154. package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
  155. package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
  156. package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
  157. package/dist/context/apiContract/graphqlSupport.js +671 -0
  158. package/dist/context/apiContract/graphqlSupport.js.map +1 -0
  159. package/dist/context/apiContract/index.d.ts +14 -0
  160. package/dist/context/apiContract/index.d.ts.map +1 -0
  161. package/dist/context/apiContract/index.js +17 -0
  162. package/dist/context/apiContract/index.js.map +1 -0
  163. package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
  164. package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
  165. package/dist/context/apiContract/webSocketSupport.js +465 -0
  166. package/dist/context/apiContract/webSocketSupport.js.map +1 -0
  167. package/dist/context/apiContractContext.d.ts +15 -0
  168. package/dist/context/apiContractContext.d.ts.map +1 -0
  169. package/dist/context/apiContractContext.js +979 -0
  170. package/dist/context/apiContractContext.js.map +1 -0
  171. package/dist/context/apiContractExtraction.d.ts +52 -0
  172. package/dist/context/apiContractExtraction.d.ts.map +1 -0
  173. package/dist/context/apiContractExtraction.js +438 -0
  174. package/dist/context/apiContractExtraction.js.map +1 -0
  175. package/dist/context/contextLineage.d.ts +79 -0
  176. package/dist/context/contextLineage.d.ts.map +1 -0
  177. package/dist/context/contextLineage.js +259 -0
  178. package/dist/context/contextLineage.js.map +1 -0
  179. package/dist/context/contextOrchestrator.d.ts +57 -0
  180. package/dist/context/contextOrchestrator.d.ts.map +1 -0
  181. package/dist/context/contextOrchestrator.js +162 -0
  182. package/dist/context/contextOrchestrator.js.map +1 -0
  183. package/dist/context/intentTracker.d.ts +73 -0
  184. package/dist/context/intentTracker.d.ts.map +1 -0
  185. package/dist/context/intentTracker.js +168 -0
  186. package/dist/context/intentTracker.js.map +1 -0
  187. package/dist/context/projectContext.d.ts +219 -0
  188. package/dist/context/projectContext.d.ts.map +1 -0
  189. package/dist/context/projectContext.js +1984 -0
  190. package/dist/context/projectContext.js.map +1 -0
  191. package/dist/prompts/index.d.ts +17 -0
  192. package/dist/prompts/index.d.ts.map +1 -0
  193. package/dist/prompts/index.js +260 -0
  194. package/dist/prompts/index.js.map +1 -0
  195. package/dist/prompts/library.d.ts +51 -0
  196. package/dist/prompts/library.d.ts.map +1 -0
  197. package/dist/prompts/library.js +65 -0
  198. package/dist/prompts/library.js.map +1 -0
  199. package/dist/prompts/templates.d.ts +44 -0
  200. package/dist/prompts/templates.d.ts.map +1 -0
  201. package/dist/prompts/templates.js +97 -0
  202. package/dist/prompts/templates.js.map +1 -0
  203. package/dist/queue/jobPersistence.d.ts +46 -0
  204. package/dist/queue/jobPersistence.d.ts.map +1 -0
  205. package/dist/queue/jobPersistence.js +158 -0
  206. package/dist/queue/jobPersistence.js.map +1 -0
  207. package/dist/queue/jobQueue.d.ts +116 -0
  208. package/dist/queue/jobQueue.d.ts.map +1 -0
  209. package/dist/queue/jobQueue.js +275 -0
  210. package/dist/queue/jobQueue.js.map +1 -0
  211. package/dist/queue/validationJob.d.ts +69 -0
  212. package/dist/queue/validationJob.d.ts.map +1 -0
  213. package/dist/queue/validationJob.js +435 -0
  214. package/dist/queue/validationJob.js.map +1 -0
  215. package/dist/resources/index.d.ts +15 -0
  216. package/dist/resources/index.d.ts.map +1 -0
  217. package/dist/resources/index.js +328 -0
  218. package/dist/resources/index.js.map +1 -0
  219. package/dist/resources/validationReportStore.d.ts +170 -0
  220. package/dist/resources/validationReportStore.d.ts.map +1 -0
  221. package/dist/resources/validationReportStore.js +515 -0
  222. package/dist/resources/validationReportStore.js.map +1 -0
  223. package/dist/server.d.ts +12 -0
  224. package/dist/server.d.ts.map +1 -0
  225. package/dist/server.js +102 -0
  226. package/dist/server.js.map +1 -0
  227. package/dist/tools/asyncValidation.d.ts +19 -0
  228. package/dist/tools/asyncValidation.d.ts.map +1 -0
  229. package/dist/tools/asyncValidation.js +346 -0
  230. package/dist/tools/asyncValidation.js.map +1 -0
  231. package/dist/tools/buildContext.d.ts +17 -0
  232. package/dist/tools/buildContext.d.ts.map +1 -0
  233. package/dist/tools/buildContext.js +188 -0
  234. package/dist/tools/buildContext.js.map +1 -0
  235. package/dist/tools/getDependencyGraph.d.ts +16 -0
  236. package/dist/tools/getDependencyGraph.d.ts.map +1 -0
  237. package/dist/tools/getDependencyGraph.js +436 -0
  238. package/dist/tools/getDependencyGraph.js.map +1 -0
  239. package/dist/tools/incrementalValidation.d.ts +71 -0
  240. package/dist/tools/incrementalValidation.d.ts.map +1 -0
  241. package/dist/tools/incrementalValidation.js +203 -0
  242. package/dist/tools/incrementalValidation.js.map +1 -0
  243. package/dist/tools/index.d.ts +24 -0
  244. package/dist/tools/index.d.ts.map +1 -0
  245. package/dist/tools/index.js +106 -0
  246. package/dist/tools/index.js.map +1 -0
  247. package/dist/tools/validateCode.d.ts +17 -0
  248. package/dist/tools/validateCode.d.ts.map +1 -0
  249. package/dist/tools/validateCode.js +368 -0
  250. package/dist/tools/validateCode.js.map +1 -0
  251. package/dist/tools/validateCodeLite.d.ts +2 -0
  252. package/dist/tools/validateCodeLite.d.ts.map +1 -0
  253. package/dist/tools/validateCodeLite.js +2 -0
  254. package/dist/tools/validateCodeLite.js.map +1 -0
  255. package/dist/tools/validation/builtins.d.ts +92 -0
  256. package/dist/tools/validation/builtins.d.ts.map +1 -0
  257. package/dist/tools/validation/builtins.js +2184 -0
  258. package/dist/tools/validation/builtins.js.map +1 -0
  259. package/dist/tools/validation/contextualNaming.d.ts +99 -0
  260. package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
  261. package/dist/tools/validation/contextualNaming.js +959 -0
  262. package/dist/tools/validation/contextualNaming.js.map +1 -0
  263. package/dist/tools/validation/deadCode.d.ts +115 -0
  264. package/dist/tools/validation/deadCode.d.ts.map +1 -0
  265. package/dist/tools/validation/deadCode.js +861 -0
  266. package/dist/tools/validation/deadCode.js.map +1 -0
  267. package/dist/tools/validation/extractors/index.d.ts +131 -0
  268. package/dist/tools/validation/extractors/index.d.ts.map +1 -0
  269. package/dist/tools/validation/extractors/index.js +233 -0
  270. package/dist/tools/validation/extractors/index.js.map +1 -0
  271. package/dist/tools/validation/extractors/javascript.d.ts +73 -0
  272. package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
  273. package/dist/tools/validation/extractors/javascript.js +1841 -0
  274. package/dist/tools/validation/extractors/javascript.js.map +1 -0
  275. package/dist/tools/validation/extractors/python.d.ts +93 -0
  276. package/dist/tools/validation/extractors/python.d.ts.map +1 -0
  277. package/dist/tools/validation/extractors/python.js +799 -0
  278. package/dist/tools/validation/extractors/python.js.map +1 -0
  279. package/dist/tools/validation/manifest.d.ts +45 -0
  280. package/dist/tools/validation/manifest.d.ts.map +1 -0
  281. package/dist/tools/validation/manifest.js +719 -0
  282. package/dist/tools/validation/manifest.js.map +1 -0
  283. package/dist/tools/validation/parser.d.ts +58 -0
  284. package/dist/tools/validation/parser.d.ts.map +1 -0
  285. package/dist/tools/validation/parser.js +232 -0
  286. package/dist/tools/validation/parser.js.map +1 -0
  287. package/dist/tools/validation/registry.d.ts +15 -0
  288. package/dist/tools/validation/registry.d.ts.map +1 -0
  289. package/dist/tools/validation/registry.js +169 -0
  290. package/dist/tools/validation/registry.js.map +1 -0
  291. package/dist/tools/validation/scoring.d.ts +54 -0
  292. package/dist/tools/validation/scoring.d.ts.map +1 -0
  293. package/dist/tools/validation/scoring.js +242 -0
  294. package/dist/tools/validation/scoring.js.map +1 -0
  295. package/dist/tools/validation/types.d.ts +120 -0
  296. package/dist/tools/validation/types.d.ts.map +1 -0
  297. package/dist/tools/validation/types.js +11 -0
  298. package/dist/tools/validation/types.js.map +1 -0
  299. package/dist/tools/validation/unusedLocals.d.ts +36 -0
  300. package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
  301. package/dist/tools/validation/unusedLocals.js +333 -0
  302. package/dist/tools/validation/unusedLocals.js.map +1 -0
  303. package/dist/tools/validation/validation.d.ts +98 -0
  304. package/dist/tools/validation/validation.d.ts.map +1 -0
  305. package/dist/tools/validation/validation.js +1837 -0
  306. package/dist/tools/validation/validation.js.map +1 -0
  307. package/dist/types/codeGraph.d.ts +163 -0
  308. package/dist/types/codeGraph.d.ts.map +1 -0
  309. package/dist/types/codeGraph.js +9 -0
  310. package/dist/types/codeGraph.js.map +1 -0
  311. package/dist/types/symbolGraph.d.ts +68 -0
  312. package/dist/types/symbolGraph.d.ts.map +1 -0
  313. package/dist/types/symbolGraph.js +10 -0
  314. package/dist/types/symbolGraph.js.map +1 -0
  315. package/dist/types/tools.d.ts +43 -0
  316. package/dist/types/tools.d.ts.map +1 -0
  317. package/dist/types/tools.js +7 -0
  318. package/dist/types/tools.js.map +1 -0
  319. package/dist/utils/fileFilter.d.ts +37 -0
  320. package/dist/utils/fileFilter.d.ts.map +1 -0
  321. package/dist/utils/fileFilter.js +91 -0
  322. package/dist/utils/fileFilter.js.map +1 -0
  323. package/dist/utils/gitUtils.d.ts +28 -0
  324. package/dist/utils/gitUtils.d.ts.map +1 -0
  325. package/dist/utils/gitUtils.js +81 -0
  326. package/dist/utils/gitUtils.js.map +1 -0
  327. package/dist/utils/logger.d.ts +15 -0
  328. package/dist/utils/logger.d.ts.map +1 -0
  329. package/dist/utils/logger.js +38 -0
  330. package/dist/utils/logger.js.map +1 -0
  331. package/dist/utils/serialization.d.ts +25 -0
  332. package/dist/utils/serialization.d.ts.map +1 -0
  333. package/dist/utils/serialization.js +53 -0
  334. package/dist/utils/serialization.js.map +1 -0
  335. package/package.json +90 -0
@@ -0,0 +1,1837 @@
1
+ /**
2
+ * Core Validation Logic Module
3
+ *
4
+ * This module contains the core validation algorithms for detecting hallucinations
5
+ * and validating symbols against the project context.
6
+ *
7
+ * Responsibilities:
8
+ * - Validate that imported packages exist in manifest files
9
+ * - Validate that used symbols exist in the project symbol table
10
+ * - Check parameter counts for function calls
11
+ * - Calculate confidence scores for validation issues
12
+ * - Generate reasoning explanations for each issue
13
+ * - Build symbol lookup tables from project context
14
+ * - Handle strict mode vs. non-strict mode validation
15
+ *
16
+ * @format
17
+ */
18
+ import * as path from "path";
19
+ import { resolveImport } from "../../context/projectContext.js";
20
+ import { extractSymbolsAST, collectLocalDefinitionsAST } from "./extractors/index.js";
21
+ import { parseCodeCached } from "./parser.js";
22
+ import { suggestSimilar, extractSimilarSymbols } from "./scoring.js";
23
+ import { isPythonSymbolExported, getPythonPipNameForImport } from "./manifest.js";
24
+ import { isJSBuiltin, isPythonBuiltin, isTSBuiltinType, NODE_BUILTIN_MODULES, } from "./builtins.js";
25
+ import { usagePatternAnalyzer } from "../../analyzers/usagePatterns.js";
26
+ import { isContextuallyValid, } from "./contextualNaming.js";
27
+ import { checkPackageRegistry } from "./registry.js";
28
+ // ============================================================================
29
+ // Manifest Validation (Tier 0)
30
+ // ============================================================================
31
+ /**
32
+ * Validate that all imported packages exist in manifest files.
33
+ * Checks package.json for JavaScript/TypeScript and requirements.txt/pyproject.toml for Python.
34
+ *
35
+ * @param imports - Array of import statements extracted from code
36
+ * @param manifest - Manifest dependencies loaded from package files
37
+ * @param newCode - The source code being validated (for extracting line content)
38
+ * @param language - Programming language (default: typescript)
39
+ * @returns Array of validation issues for missing dependencies
40
+ */
41
+ export async function validateManifest(imports, manifest, newCode, language = "typescript", filePath = "") {
42
+ const issues = [];
43
+ // Phase 1: Collect all unknown packages that need registry lookup
44
+ const unknownPackages = [];
45
+ for (const imp of imports) {
46
+ if (!imp.isExternal)
47
+ continue;
48
+ const pkgName = getPackageName(imp.module, language);
49
+ // Skip Node.js built-ins
50
+ if (imp.module.startsWith("node:") || NODE_BUILTIN_MODULES.has(pkgName)) {
51
+ continue;
52
+ }
53
+ // Check if package is in manifest
54
+ if (!manifest.all.has(pkgName)) {
55
+ const scopedName = imp.module.startsWith("@") ?
56
+ imp.module.split("/").slice(0, 2).join("/")
57
+ : pkgName;
58
+ if (!manifest.all.has(scopedName)) {
59
+ // For Python, check if this import name is a known alias of a pip package
60
+ // (e.g. "dateutil" -> "python-dateutil", "pythonjsonlogger" -> "python-json-logger")
61
+ // These are commonly installed as transitive deps and shouldn't be flagged
62
+ if (language === "python" && getPythonPipNameForImport(pkgName)) {
63
+ continue;
64
+ }
65
+ unknownPackages.push({ imp, pkgName, scopedName });
66
+ }
67
+ }
68
+ }
69
+ if (unknownPackages.length === 0)
70
+ return issues;
71
+ // Phase 2: Batch registry lookups — deduplicate and check in parallel
72
+ const uniquePkgNames = [...new Set(unknownPackages.map(u => u.pkgName))];
73
+ const REGISTRY_BATCH_SIZE = 10;
74
+ const registryResults = new Map();
75
+ for (let i = 0; i < uniquePkgNames.length; i += REGISTRY_BATCH_SIZE) {
76
+ const batch = uniquePkgNames.slice(i, i + REGISTRY_BATCH_SIZE);
77
+ const results = await Promise.all(batch.map(async (pkgName) => ({
78
+ pkgName,
79
+ exists: await checkPackageRegistry(pkgName, language),
80
+ })));
81
+ for (const { pkgName, exists } of results) {
82
+ registryResults.set(pkgName, exists);
83
+ }
84
+ }
85
+ // Phase 3: Build issues from cached results
86
+ for (const { imp, pkgName } of unknownPackages) {
87
+ const existsInRegistry = registryResults.get(pkgName) ?? false;
88
+ if (existsInRegistry) {
89
+ const installCmd = language === "python"
90
+ ? `Run: pip install ${pkgName} (or add to requirements.txt)`
91
+ : `Run: npm install ${pkgName}`;
92
+ issues.push({
93
+ type: "missingDependency",
94
+ severity: "low",
95
+ message: `Package '${pkgName}' is not installed (but exists on registry)`,
96
+ line: imp.line,
97
+ file: filePath,
98
+ code: getLineFromCode(newCode, imp.line),
99
+ suggestion: installCmd,
100
+ confidence: 100,
101
+ reasoning: `Package not found in manifest, but verified to exist on ${language} registry. Safe to install.`,
102
+ });
103
+ }
104
+ else {
105
+ issues.push({
106
+ type: "dependencyHallucination",
107
+ severity: "critical",
108
+ message: `Package '${imp.module}' does not exist on ${language} registry`,
109
+ line: imp.line,
110
+ file: filePath,
111
+ code: getLineFromCode(newCode, imp.line),
112
+ suggestion: `Did you mean: ${suggestSimilar(pkgName, Array.from(manifest.all)) || "unknown"}?`,
113
+ confidence: 99,
114
+ reasoning: `Package not found in manifest AND lookup failed on registry. This is likely a hallucination.`,
115
+ });
116
+ }
117
+ }
118
+ return issues;
119
+ }
120
+ /**
121
+ * Extract package name from import path.
122
+ * Handles scoped packages correctly and Python submodules.
123
+ *
124
+ * @param importPath - The import path (e.g., '@scope/package/path' or 'package/path' or 'package.submodule')
125
+ * @param language - Programming language to determine submodule handling (optional)
126
+ * @returns The base package name
127
+ */
128
+ function getPackageName(importPath, language) {
129
+ // Handle scoped packages: @scope/package/path -> @scope/package
130
+ if (importPath.startsWith("@")) {
131
+ const parts = importPath.split("/");
132
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importPath;
133
+ }
134
+ // For Python, handle dot notation for submodules: package.submodule -> package
135
+ // This is critical because Python uses dots for submodules (e.g., fastapi.middleware.cors)
136
+ // while JavaScript/TypeScript uses slashes (e.g., package/submodule)
137
+ if (language === "python") {
138
+ return importPath.split(".")[0];
139
+ }
140
+ // Regular packages: package/path -> package
141
+ return importPath.split("/")[0];
142
+ }
143
+ /**
144
+ * Extract a specific line from code by line number.
145
+ *
146
+ * @param code - The source code
147
+ * @param lineNum - Line number (1-indexed)
148
+ * @returns The trimmed line content
149
+ */
150
+ export function getLineFromCode(code, lineNum) {
151
+ const lines = code.split("\n");
152
+ return lines[lineNum - 1]?.trim() || "";
153
+ }
154
+ // ============================================================================
155
+ // Symbol Table Building
156
+ // ============================================================================
157
+ /**
158
+ * Build a symbol table from project context for validation.
159
+ * Converts the project context symbol index into a flat array of ProjectSymbol objects.
160
+ *
161
+ * @param context - The project context containing all symbols
162
+ * @param relevantSymbols - Optional list of symbol names to include (for smart context filtering)
163
+ * @returns Array of project symbols for validation
164
+ */
165
+ export function buildSymbolTable(context, relevantSymbols) {
166
+ const symbols = [];
167
+ const relevantSet = relevantSymbols ? new Set(relevantSymbols) : null;
168
+ for (const [name, definitions] of context.symbolIndex) {
169
+ // If smart context is enabled, only include relevant symbols
170
+ if (relevantSet && !relevantSet.has(name)) {
171
+ continue;
172
+ }
173
+ for (const def of definitions) {
174
+ symbols.push({
175
+ name,
176
+ type: mapSymbolKind(def.symbol.kind),
177
+ file: def.file,
178
+ line: def.symbol.line,
179
+ params: def.symbol.params?.map((p) => p.name),
180
+ paramCount: def.symbol.params?.length,
181
+ scope: def.symbol.scope,
182
+ });
183
+ }
184
+ }
185
+ return symbols;
186
+ }
187
+ /**
188
+ * Map symbol kind from project context to ProjectSymbol type.
189
+ *
190
+ * @param kind - The symbol kind from project context
191
+ * @returns The mapped ProjectSymbol type
192
+ */
193
+ function mapSymbolKind(kind) {
194
+ switch (kind) {
195
+ case "function":
196
+ case "hook":
197
+ return "function";
198
+ case "class":
199
+ case "component":
200
+ return "class";
201
+ case "interface":
202
+ case "type":
203
+ case "enum":
204
+ case "variable":
205
+ case "route":
206
+ return "variable";
207
+ default:
208
+ return "method";
209
+ }
210
+ }
211
+ // ============================================================================
212
+ // Confidence Scoring and Reasoning
213
+ // ============================================================================
214
+ /**
215
+ * Calculate confidence score for a validation issue.
216
+ * Based on multiple factors: similarity to existing symbols, context, etc.
217
+ *
218
+ * @param options - Configuration for confidence calculation
219
+ * @returns Object containing confidence score (0-100) and reasoning explanation
220
+ */
221
+ export function calculateConfidence(options) {
222
+ const { issueType, symbolName, similarSymbols, existsInProject, strictMode } = options;
223
+ let confidence = 0;
224
+ let reasoning = "";
225
+ switch (issueType) {
226
+ case "nonExistentFunction":
227
+ case "nonExistentClass":
228
+ if (existsInProject && strictMode) {
229
+ // Symbol exists but not imported
230
+ confidence = 90;
231
+ reasoning = `Symbol '${symbolName}' found in project but not imported. High confidence this is a missing import.`;
232
+ }
233
+ else if (similarSymbols.length === 0) {
234
+ // No similar symbols at all
235
+ confidence = 95;
236
+ reasoning = `Searched entire project, found no symbol named '${symbolName}' or similar. Very high confidence this is a hallucination.`;
237
+ }
238
+ else if (similarSymbols.length === 1) {
239
+ // One very similar symbol (likely typo)
240
+ confidence = 92;
241
+ reasoning = `Found very similar symbol: ${similarSymbols[0]}. High confidence this is a typo.`;
242
+ }
243
+ else {
244
+ // Multiple similar symbols
245
+ confidence = 85;
246
+ reasoning = `Found ${similarSymbols.length} similar symbols. Likely a typo or wrong function name.`;
247
+ }
248
+ break;
249
+ case "dependencyHallucination":
250
+ confidence = 95;
251
+ reasoning = `Package not found in manifest. This will cause import errors at runtime.`;
252
+ break;
253
+ case "wrongParamCount":
254
+ confidence = 88;
255
+ reasoning = `Parameter count mismatch detected via AST analysis. High confidence this will cause runtime errors.`;
256
+ break;
257
+ case "nonExistentMethod":
258
+ confidence = 70;
259
+ reasoning = `Method not found on object. Medium confidence - may be dynamic or inherited.`;
260
+ break;
261
+ case "nonExistentImport":
262
+ confidence = 93;
263
+ reasoning = `Imported symbol not found in target module. High confidence this will fail at runtime.`;
264
+ break;
265
+ case "undefinedVariable":
266
+ confidence = 90;
267
+ reasoning = `Variable '${symbolName}' is used but not defined or imported. High confidence this is a hallucination.`;
268
+ break;
269
+ case "unusedImport":
270
+ confidence = 98;
271
+ reasoning = `Imported symbol '${symbolName}' is never used in the code. Very high confidence.`;
272
+ break;
273
+ default:
274
+ confidence = 75;
275
+ reasoning = `Issue detected via static analysis.`;
276
+ }
277
+ return { confidence, reasoning };
278
+ }
279
+ // ============================================================================
280
+ // Symbol Validation (Tier 1)
281
+ // ============================================================================
282
+ /**
283
+ * Validate that all used symbols exist in the project symbol table.
284
+ * Performs comprehensive validation including:
285
+ * - Function calls
286
+ * - Method calls
287
+ * - Class instantiations
288
+ * - Parameter count checking
289
+ * - Import validation
290
+ * - Python __all__ export validation
291
+ *
292
+ * @param usedSymbols - Array of symbol usages extracted from code
293
+ * @param symbolTable - Array of project symbols for validation
294
+ * @param newCode - The source code being validated
295
+ * @param language - Programming language ('python', 'javascript', 'typescript')
296
+ * @param strictMode - If true, requires explicit imports for all symbols
297
+ * @param imports - Array of import statements (for internal import validation)
298
+ * @param pythonExports - Map of Python module exports (for __all__ validation)
299
+ * @param typeReferences - Optional array of type references (for unused import detection)
300
+ * @returns Array of validation issues
301
+ */
302
+ export function validateSymbols(usedSymbols, symbolTable, newCode, language, strictMode, imports = [], pythonExports = new Map(), context = null, // Added context
303
+ filePath = "", // Added file path
304
+ missingPackages = new Set(), // Added missing packages for smart validation
305
+ typeReferences = []) {
306
+ const issues = [];
307
+ // Build lookup maps for PROJECT symbols
308
+ // Use arrays to handle multiple symbols with same name (different scopes)
309
+ const projectFunctions = new Map();
310
+ const projectClasses = new Map();
311
+ const projectMethods = new Map();
312
+ const projectVariables = new Map(); // includes interfaces, types, enums
313
+ for (const sym of symbolTable) {
314
+ if (sym.type === "function") {
315
+ const existing = projectFunctions.get(sym.name) || [];
316
+ existing.push(sym);
317
+ projectFunctions.set(sym.name, existing);
318
+ }
319
+ else if (sym.type === "class") {
320
+ projectClasses.set(sym.name, sym);
321
+ }
322
+ else if (sym.type === "method") {
323
+ const existing = projectMethods.get(sym.name) || [];
324
+ existing.push(sym);
325
+ projectMethods.set(sym.name, existing);
326
+ }
327
+ else if (sym.type === "variable") {
328
+ projectVariables.set(sym.name, sym);
329
+ }
330
+ }
331
+ // When smart context filtering is enabled, symbolTable may not include all symbols.
332
+ // For certain checks (especially internal-import method validation), we consult the
333
+ // full ProjectContext symbol index as a fallback.
334
+ const hasContextClass = (name) => {
335
+ if (!context)
336
+ return false;
337
+ const defs = context.symbolIndex.get(name);
338
+ if (!defs)
339
+ return false;
340
+ for (const def of defs) {
341
+ const kind = def?.symbol?.kind;
342
+ if (kind === "class" || kind === "component")
343
+ return true;
344
+ }
345
+ return false;
346
+ };
347
+ const getContextSymbolsByName = (name) => {
348
+ if (!context)
349
+ return [];
350
+ const defs = context.symbolIndex.get(name);
351
+ if (!defs)
352
+ return [];
353
+ const out = [];
354
+ for (const def of defs) {
355
+ out.push({
356
+ name,
357
+ type: mapSymbolKind(def.symbol.kind),
358
+ file: def.file,
359
+ line: def.symbol.line,
360
+ params: def.symbol.params?.map((p) => p.name),
361
+ paramCount: def.symbol.params?.length,
362
+ scope: def.symbol.scope,
363
+ });
364
+ }
365
+ return out;
366
+ };
367
+ // Build lookup maps for VALID symbols
368
+ const validFunctions = new Map();
369
+ const validClasses = new Map();
370
+ const validMethods = new Map();
371
+ const validVariables = new Map();
372
+ // Helper to get first matching symbol by scope
373
+ const getMatchingSymbol = (symbols, objectName) => {
374
+ if (!symbols || symbols.length === 0)
375
+ return undefined;
376
+ if (symbols.length === 1) {
377
+ // Single symbol - check if scope matches (if it has one)
378
+ const sym = symbols[0];
379
+ if (!sym.scope || !objectName || sym.scope === objectName) {
380
+ return sym;
381
+ }
382
+ return undefined;
383
+ }
384
+ // Multiple symbols - find one with matching scope, or one without scope (general method)
385
+ for (const sym of symbols) {
386
+ if (sym.scope === objectName)
387
+ return sym; // Exact scope match
388
+ if (!sym.scope)
389
+ return sym; // General method (no scope)
390
+ }
391
+ return undefined;
392
+ };
393
+ // In non-strict mode: all project symbols are valid (backwards compatible)
394
+ if (!strictMode) {
395
+ for (const [name, syms] of projectFunctions) {
396
+ validFunctions.set(name, syms[0]); // Use first function as representative
397
+ }
398
+ for (const [name, sym] of projectClasses)
399
+ validClasses.set(name, sym);
400
+ for (const [name, syms] of projectMethods) {
401
+ validMethods.set(name, syms[0]); // Use first method as representative
402
+ }
403
+ for (const [name, sym] of projectVariables)
404
+ validVariables.set(name, sym);
405
+ }
406
+ // Tier 1: Add symbols defined in the new code itself (including parameters, destructured variables)
407
+ // These MUST TAKE PRECEDENCE over project symbols to avoid false positives on local scope
408
+ const newCodeSymbols = extractSymbolsAST(newCode, "(new code)", language, {
409
+ includeParameterSymbols: true,
410
+ });
411
+ for (const sym of newCodeSymbols) {
412
+ const projectSym = {
413
+ name: sym.name,
414
+ type: sym.type === "interface" || sym.type === "type" ?
415
+ "variable"
416
+ : sym.type,
417
+ file: "(new code)",
418
+ params: sym.params,
419
+ paramCount: sym.paramCount,
420
+ };
421
+ switch (sym.type) {
422
+ case "function":
423
+ validFunctions.set(sym.name, projectSym);
424
+ break;
425
+ case "class":
426
+ validClasses.set(sym.name, projectSym);
427
+ break;
428
+ case "method":
429
+ validMethods.set(sym.name, projectSym);
430
+ break;
431
+ case "variable":
432
+ case "interface":
433
+ case "type":
434
+ validVariables.set(sym.name, projectSym);
435
+ // Variables can be called if they hold functions (e.g., destructured from hooks or params)
436
+ validFunctions.set(sym.name, projectSym);
437
+ break;
438
+ }
439
+ }
440
+ // Tier 2: Add imported symbols (both internal and external)
441
+ for (const imp of imports) {
442
+ if (!imp.isExternal) {
443
+ // Internal imports: Validate against project symbol table AND precise module resolution
444
+ // Attempt module resolution if context is available AND we have a real file path
445
+ // Skip resolution for empty filePath (code snippets without known location)
446
+ let resolvedFile = null;
447
+ if (context && filePath && filePath.trim()) {
448
+ resolvedFile = resolveImport(imp.module, filePath, Array.from(context.files.keys()));
449
+ // Python-specific: convert dot notation to path separators
450
+ if (!resolvedFile && language === "python") {
451
+ if (imp.module.startsWith(".")) {
452
+ // Relative import: from ..models.deliverable import X
453
+ // Count leading dots to determine how many directories to go up
454
+ const dotMatch = imp.module.match(/^(\.+)/);
455
+ const dotCount = dotMatch ? dotMatch[1].length : 0;
456
+ const remainder = imp.module.slice(dotCount);
457
+ // Go up (dotCount) directories from current file's package directory
458
+ let baseDir = filePath.substring(0, filePath.lastIndexOf("/"));
459
+ for (let i = 1; i < dotCount; i++) {
460
+ baseDir = baseDir.substring(0, baseDir.lastIndexOf("/"));
461
+ }
462
+ if (remainder) {
463
+ const modPath = remainder.replace(/\./g, "/");
464
+ const pyFile = path.join(baseDir, `${modPath}.py`);
465
+ const pyInit = path.join(baseDir, modPath, "__init__.py");
466
+ if (context.files.has(pyFile)) {
467
+ resolvedFile = pyFile;
468
+ }
469
+ else if (context.files.has(pyInit)) {
470
+ resolvedFile = pyInit;
471
+ }
472
+ }
473
+ else {
474
+ // "from . import X" — resolve to current package's __init__.py
475
+ const pyInit = path.join(baseDir, "__init__.py");
476
+ if (context.files.has(pyInit)) {
477
+ resolvedFile = pyInit;
478
+ }
479
+ }
480
+ }
481
+ else {
482
+ // Absolute import: from app.core.config import X
483
+ const modulePath = imp.module.replace(/\./g, "/");
484
+ const basePath = context.projectPath;
485
+ const pyFile = path.join(basePath, `${modulePath}.py`);
486
+ const pyInit = path.join(basePath, modulePath, "__init__.py");
487
+ if (context.files.has(pyFile)) {
488
+ resolvedFile = pyFile;
489
+ }
490
+ else if (context.files.has(pyInit)) {
491
+ resolvedFile = pyInit;
492
+ }
493
+ else {
494
+ // Full-stack fallback: the Python root may be a subdirectory
495
+ // (e.g., report/backend/ instead of report/). Walk up from the
496
+ // file being validated to find the directory that resolves the import.
497
+ let tryDir = path.dirname(filePath);
498
+ while (tryDir.length > basePath.length) {
499
+ const tryPy = path.join(tryDir, `${modulePath}.py`);
500
+ const tryInit = path.join(tryDir, modulePath, "__init__.py");
501
+ if (context.files.has(tryPy)) {
502
+ resolvedFile = tryPy;
503
+ break;
504
+ }
505
+ else if (context.files.has(tryInit)) {
506
+ resolvedFile = tryInit;
507
+ break;
508
+ }
509
+ tryDir = path.dirname(tryDir);
510
+ }
511
+ }
512
+ }
513
+ }
514
+ }
515
+ for (const name of imp.names) {
516
+ // If we resolved the file, check its exports directly (Robust Check)
517
+ if (resolvedFile && context) {
518
+ const fileInfo = context.files.get(resolvedFile);
519
+ if (fileInfo) {
520
+ // Check exact exports
521
+ const exports = fileInfo.exports;
522
+ const hasExport = exports.some((e) => e.name === name.imported ||
523
+ (name.imported === "default" && e.isDefault));
524
+ if (!hasExport) {
525
+ // Double check against all symbols in file marked as exported
526
+ // (sometimes exports are implicit in simple extractors)
527
+ // For Python: ALL module-level names are importable (no export keyword)
528
+ let symExport = fileInfo.symbols.find((s) => (language === "python" || s.exported) && s.name === name.imported);
529
+ // For Python: check if the symbol is imported at module level in the target file.
530
+ // In Python, any name imported at module level is part of the module's namespace
531
+ // and is importable by other modules (e.g., `from app.api.cli_tokens import CLIToken`
532
+ // works if cli_tokens.py has `from app.models.cli_token import CLIToken`).
533
+ if (!symExport && language === "python") {
534
+ const isImportedInModule = fileInfo.imports.some((modImp) => modImp.namedImports.includes(name.imported) ||
535
+ modImp.defaultImport === name.imported);
536
+ if (isImportedInModule) {
537
+ // Treat as found — symbol is a valid Python namespace member via re-import
538
+ symExport = { name: name.imported, kind: "variable", line: 0, exported: true };
539
+ }
540
+ }
541
+ if (!symExport) {
542
+ // For Python: check if the imported name is a sub-module file
543
+ // e.g., "from app.api import auth" where app/api/auth.py exists
544
+ if (language === "python" && context) {
545
+ const resolvedDir = resolvedFile.endsWith("__init__.py")
546
+ ? path.dirname(resolvedFile)
547
+ : path.dirname(resolvedFile);
548
+ const subModPy = path.join(resolvedDir, `${name.imported}.py`);
549
+ const subModInit = path.join(resolvedDir, name.imported, "__init__.py");
550
+ if (context.files.has(subModPy) || context.files.has(subModInit)) {
551
+ // Valid sub-module import
552
+ continue;
553
+ }
554
+ }
555
+ // For Python: check pythonExports (__all__) for the module
556
+ // At this point, the symbol was NOT found in fileInfo.symbols.
557
+ // The only way it can still be valid is if __all__ explicitly lists it
558
+ // (meaning it was re-exported via star import or dynamic assignment).
559
+ if (language === "python") {
560
+ // Convert resolved file to module path for pythonExports lookup
561
+ const basePath = context?.projectPath || "";
562
+ const relPath = resolvedFile.startsWith(basePath)
563
+ ? resolvedFile.slice(basePath.length + 1)
564
+ : resolvedFile;
565
+ // Convert file path to Python module path: app/utils/git_providers/__init__.py -> app.utils.git_providers
566
+ const pyModPath = relPath
567
+ .replace(/__init__\.py$/, "")
568
+ .replace(/\.py$/, "")
569
+ .replace(/\//g, ".")
570
+ .replace(/\.$/, "");
571
+ const moduleAllExports = pythonExports.get(pyModPath);
572
+ if (moduleAllExports && moduleAllExports.has(name.imported)) {
573
+ continue; // Symbol is explicitly in __all__ (re-exported) — valid
574
+ }
575
+ // Symbol is NOT in fileInfo.symbols AND NOT in __all__
576
+ // Fall through to flag as nonExistentImport
577
+ }
578
+ // This is a TRUE hallucination - module exists, but export doesn't
579
+ const allExports = exports
580
+ .map((e) => e.name)
581
+ .concat(fileInfo.symbols
582
+ .filter((s) => language === "python" || s.exported)
583
+ .map((s) => s.name));
584
+ const suggestion = suggestSimilar(name.imported, allExports);
585
+ issues.push({
586
+ type: "nonExistentImport",
587
+ severity: "critical",
588
+ message: `Module '${imp.module}' exists but has no export named '${name.imported}'`,
589
+ line: imp.line,
590
+ file: filePath,
591
+ code: getLineFromCode(newCode, imp.line),
592
+ suggestion,
593
+ confidence: 99,
594
+ reasoning: `Resolved module to ${resolvedFile}, but it does not export '${name.imported}'.`,
595
+ });
596
+ continue; // Skip further checks for this symbol
597
+ }
598
+ }
599
+ // If we found the export, map it to valid symbols
600
+ // We need to find the symbol in projectFunctions etc. to add it to 'valid' maps
601
+ // But if we can't find it in the global map (e.g. default export unnamed),
602
+ // we still mark the LOCAL name as valid because we verified the export exists.
603
+ // Construct a synthetic valid symbol if not found in global map
604
+ const validSym = {
605
+ name: name.local,
606
+ type: "variable", // fallback
607
+ file: resolvedFile,
608
+ };
609
+ validVariables.set(name.local, validSym);
610
+ validFunctions.set(name.local, { ...validSym, type: "function" });
611
+ validClasses.set(name.local, { ...validSym, type: "class" });
612
+ // Also try to find real symbol for better type info
613
+ const funcSyms = projectFunctions.get(name.imported);
614
+ const realSym = (funcSyms && funcSyms[0]) ||
615
+ projectClasses.get(name.imported) ||
616
+ projectVariables.get(name.imported);
617
+ if (realSym) {
618
+ if (realSym.type === "function")
619
+ validFunctions.set(name.local, realSym);
620
+ else if (realSym.type === "class")
621
+ validClasses.set(name.local, realSym);
622
+ else
623
+ validVariables.set(name.local, realSym);
624
+ }
625
+ continue; // Successfully validated
626
+ }
627
+ }
628
+ // If it was a relative import and we couldn't resolve the file, THAT IS A HALLUCINATION
629
+ // We should NOT fall back to global lookup for relative imports if context is available
630
+ // BUT: Only enforce this if we have a real file path (not empty/scratchpad)
631
+ if (context &&
632
+ filePath &&
633
+ filePath.trim() &&
634
+ imp.module.startsWith(".")) {
635
+ if (!resolvedFile) {
636
+ issues.push({
637
+ type: "nonExistentImport",
638
+ severity: "critical",
639
+ message: `Module '${imp.module}' found in import does not exist`,
640
+ line: imp.line,
641
+ file: filePath,
642
+ code: getLineFromCode(newCode, imp.line),
643
+ suggestion: "Check the relative file path",
644
+ confidence: 99,
645
+ reasoning: `Could not resolve relative import path '${imp.module}' from '${filePath}'. File does not exist.`,
646
+ });
647
+ continue;
648
+ }
649
+ }
650
+ // FALLBACK: Old global lookup logic (only for non-relative or if context missing)
651
+ const importedFuncSyms = projectFunctions.get(name.imported);
652
+ const localFuncSyms = projectFunctions.get(name.local);
653
+ const projectSym = (importedFuncSyms && importedFuncSyms[0]) ||
654
+ (localFuncSyms && localFuncSyms[0]) ||
655
+ projectClasses.get(name.imported) ||
656
+ projectClasses.get(name.local) ||
657
+ projectVariables.get(name.imported) ||
658
+ projectVariables.get(name.local);
659
+ // For Python, also check if the symbol is in __all__ of the module
660
+ // But skip this check if the imported name is a sub-module file
661
+ if (language === "python" && projectSym) {
662
+ // First check if it's a sub-module file (sub-modules don't need __all__)
663
+ if (context) {
664
+ const moduleDir = imp.module.replace(/^\.+/, "").replace(/\./g, "/");
665
+ const basePath = context.projectPath;
666
+ let subModFound = false;
667
+ const subModPy = path.join(basePath, moduleDir, `${name.imported}.py`);
668
+ const subModInit = path.join(basePath, moduleDir, name.imported, "__init__.py");
669
+ if (context.files.has(subModPy) || context.files.has(subModInit)) {
670
+ subModFound = true;
671
+ }
672
+ else {
673
+ // Full-stack fallback: walk up from file dir
674
+ let tryDir = path.dirname(filePath);
675
+ while (!subModFound && tryDir.length > basePath.length) {
676
+ if (context.files.has(path.join(tryDir, moduleDir, `${name.imported}.py`)) ||
677
+ context.files.has(path.join(tryDir, moduleDir, name.imported, "__init__.py"))) {
678
+ subModFound = true;
679
+ }
680
+ tryDir = path.dirname(tryDir);
681
+ }
682
+ }
683
+ if (subModFound) {
684
+ // Valid sub-module import, skip __all__ check
685
+ validVariables.set(name.local, projectSym);
686
+ continue;
687
+ }
688
+ }
689
+ const modulePath = imp.module
690
+ .replace(/^\.+/, "")
691
+ .replace(/[/\\]/g, ".");
692
+ if (!isPythonSymbolExported(modulePath, name.imported, pythonExports)) {
693
+ const { confidence, reasoning } = calculateConfidence({
694
+ issueType: "nonExistentImport",
695
+ symbolName: name.imported,
696
+ similarSymbols: [],
697
+ existsInProject: true,
698
+ strictMode,
699
+ });
700
+ issues.push({
701
+ type: "nonExistentImport",
702
+ severity: "critical",
703
+ message: `Symbol '${name.imported}' exists but is not exported in __all__ of '${imp.module}'`,
704
+ line: imp.line,
705
+ file: filePath,
706
+ code: getLineFromCode(newCode, imp.line),
707
+ suggestion: `Add '${name.imported}' to __all__ in ${imp.module}/__init__.py, or import directly from the submodule`,
708
+ confidence,
709
+ reasoning: `Symbol found in module but not in __all__ export list. ${reasoning}`,
710
+ });
711
+ continue;
712
+ }
713
+ }
714
+ if (projectSym) {
715
+ if (projectSym.type === "function")
716
+ validFunctions.set(name.local, projectSym);
717
+ else if (projectSym.type === "class")
718
+ validClasses.set(name.local, projectSym);
719
+ else if (projectSym.type === "variable")
720
+ validVariables.set(name.local, projectSym);
721
+ }
722
+ else {
723
+ // For Python: check if the imported name is a sub-module file
724
+ // e.g., "from app.api import auth" where app/api/auth.py exists
725
+ if (language === "python" && context) {
726
+ const modulePath = imp.module.replace(/^\.+/, "").replace(/\./g, "/");
727
+ const basePath = context.projectPath;
728
+ let resolvedSubMod = null;
729
+ const subModulePy = path.join(basePath, modulePath, `${name.imported}.py`);
730
+ const subModuleInit = path.join(basePath, modulePath, name.imported, "__init__.py");
731
+ if (context.files.has(subModulePy)) {
732
+ resolvedSubMod = subModulePy;
733
+ }
734
+ else if (context.files.has(subModuleInit)) {
735
+ resolvedSubMod = subModuleInit;
736
+ }
737
+ else {
738
+ // Full-stack fallback: walk up from file dir
739
+ let tryDir = path.dirname(filePath);
740
+ while (!resolvedSubMod && tryDir.length > basePath.length) {
741
+ const tryPy = path.join(tryDir, modulePath, `${name.imported}.py`);
742
+ const tryInit = path.join(tryDir, modulePath, name.imported, "__init__.py");
743
+ if (context.files.has(tryPy)) {
744
+ resolvedSubMod = tryPy;
745
+ }
746
+ else if (context.files.has(tryInit)) {
747
+ resolvedSubMod = tryInit;
748
+ }
749
+ tryDir = path.dirname(tryDir);
750
+ }
751
+ }
752
+ if (resolvedSubMod) {
753
+ // It's a valid sub-module import — treat as a module variable
754
+ validVariables.set(name.local, {
755
+ name: name.local,
756
+ type: "variable",
757
+ file: resolvedSubMod,
758
+ });
759
+ continue;
760
+ }
761
+ }
762
+ // Symbol not found in project - this is a hallucinated import!
763
+ const allNames = Array.from(projectFunctions.keys())
764
+ .concat(Array.from(projectClasses.keys()))
765
+ .concat(Array.from(projectVariables.keys()));
766
+ const suggestion = suggestSimilar(name.imported, allNames);
767
+ const similarSymbols = extractSimilarSymbols(suggestion);
768
+ const { confidence, reasoning } = calculateConfidence({
769
+ issueType: "nonExistentImport",
770
+ symbolName: name.imported,
771
+ similarSymbols,
772
+ existsInProject: false,
773
+ strictMode,
774
+ });
775
+ issues.push({
776
+ type: "nonExistentImport",
777
+ severity: "critical",
778
+ message: `Imported symbol '${name.imported}' does not exist in module '${imp.module}'`,
779
+ line: imp.line,
780
+ file: filePath,
781
+ code: getLineFromCode(newCode, imp.line),
782
+ suggestion,
783
+ confidence,
784
+ reasoning,
785
+ });
786
+ }
787
+ }
788
+ }
789
+ else {
790
+ // External imports: Treat as valid (manifest check was done in Tier 0)
791
+ for (const name of imp.names) {
792
+ const extSym = {
793
+ name: name.local,
794
+ type: "variable",
795
+ file: imp.module,
796
+ };
797
+ // Add to all maps as we don't know the exact type of external symbols
798
+ validVariables.set(name.local, extSym);
799
+ validFunctions.set(name.local, { ...extSym, type: "function" });
800
+ validClasses.set(name.local, { ...extSym, type: "class" });
801
+ // For Python dotted imports (e.g., `import concurrent.futures`),
802
+ // also register the base module name as valid since Python makes it available
803
+ if (language === "python" && name.local.includes(".")) {
804
+ const baseName = name.local.split(".")[0];
805
+ const baseSym = { ...extSym, name: baseName };
806
+ validVariables.set(baseName, baseSym);
807
+ validFunctions.set(baseName, { ...baseSym, type: "function" });
808
+ validClasses.set(baseName, { ...baseSym, type: "class" });
809
+ }
810
+ }
811
+ }
812
+ }
813
+ // Build set of ALL imported names (including ones that failed resolution)
814
+ // This prevents double-flagging: nonExistentImport + undefinedVariable for the same symbol
815
+ const allImportedNames = new Set();
816
+ for (const imp of imports) {
817
+ for (const name of imp.names) {
818
+ allImportedNames.add(name.local);
819
+ // For Python dotted imports: `import concurrent.futures` → also add `concurrent`
820
+ if (language === "python" && name.local.includes(".")) {
821
+ allImportedNames.add(name.local.split(".")[0]);
822
+ }
823
+ }
824
+ }
825
+ // Collect locally-defined names (function params, assignments, loop vars, etc.)
826
+ // These are local scope variables that won't appear in the project symbol table
827
+ // but are valid identifiers — prevents false undefinedVariable on method calls like db.execute()
828
+ const localDefinitions = collectLocalDefinitionsAST(newCode, language);
829
+ // Lightweight literal-type inference for locally-defined variables in the new code.
830
+ // Used to validate method calls on obvious built-in types (e.g., array literals) without
831
+ // over-flagging methods on unknown values (e.g., results of createRoot()).
832
+ const localLiteralKinds = new Map();
833
+ if (language === "javascript" || language === "typescript") {
834
+ try {
835
+ const tree = parseCodeCached(newCode, language, {
836
+ filePath: filePath || "(new code)",
837
+ useCache: false,
838
+ });
839
+ const walk = (node) => {
840
+ if (node.type === "variable_declarator") {
841
+ const nameNode = node.childForFieldName("name");
842
+ const valueNode = node.childForFieldName("value");
843
+ if (nameNode?.type === "identifier" && valueNode) {
844
+ const name = newCode.slice(nameNode.startIndex, nameNode.endIndex);
845
+ if (valueNode.type === "array" || valueNode.type === "object" || valueNode.type === "string") {
846
+ localLiteralKinds.set(name, valueNode.type);
847
+ }
848
+ }
849
+ }
850
+ for (const child of node.children || []) {
851
+ if (child)
852
+ walk(child);
853
+ }
854
+ };
855
+ walk(tree.rootNode);
856
+ }
857
+ catch {
858
+ // Best-effort inference only.
859
+ }
860
+ }
861
+ // Validate each used symbol
862
+ for (const used of usedSymbols) {
863
+ if (used.type === "call") {
864
+ const func = validFunctions.get(used.name);
865
+ const cls = validClasses.get(used.name);
866
+ const method = validMethods.get(used.name);
867
+ if (!func && !cls && !method) {
868
+ // Skip imported names — validated via import checks, not function existence
869
+ if (allImportedNames.has(used.name))
870
+ continue;
871
+ // Skip locally-defined functions (nested defs, local assignments that hold callables)
872
+ if (localDefinitions.has(used.name))
873
+ continue;
874
+ // Built-in check (Tier 1.5) - handles browser globals, standard libraries
875
+ if ((language === "python" && isPythonBuiltin(used.name)) ||
876
+ ((language === "javascript" || language === "typescript") &&
877
+ isJSBuiltin(used.name))) {
878
+ continue;
879
+ }
880
+ // SMART TEST RELAXATION:
881
+ // In test files, we assume unknown functions/variables are globals (describe, it, expect)
882
+ // or mocks, unless strict mode is explicitly forced.
883
+ if (isTestFile(filePath) && !strictMode) {
884
+ continue;
885
+ }
886
+ const funcSyms = projectFunctions.get(used.name);
887
+ const existsInProject = (funcSyms && funcSyms.length > 0) || projectClasses.has(used.name);
888
+ let suggestion = "";
889
+ if (existsInProject) {
890
+ const sym = (funcSyms && funcSyms[0]) || projectClasses.get(used.name);
891
+ if (sym) {
892
+ // Calculate a descriptive suggestion including the file
893
+ // Note: We'd ideally calculate a relative path here, but for now,
894
+ // telling the user the file name is a huge win.
895
+ suggestion = `Add: import { ${used.name} } from './${sym.file.replace(/\\/g, "/")}'`;
896
+ }
897
+ else {
898
+ suggestion = `Add: import { ${used.name} } from '...'`;
899
+ }
900
+ }
901
+ else {
902
+ suggestion = suggestSimilar(used.name, Array.from(projectFunctions.keys()));
903
+ }
904
+ const similarSymbols = extractSimilarSymbols(suggestion);
905
+ const { confidence, reasoning } = calculateConfidence({
906
+ issueType: "nonExistentFunction",
907
+ symbolName: used.name,
908
+ similarSymbols,
909
+ existsInProject,
910
+ strictMode,
911
+ });
912
+ issues.push({
913
+ type: "nonExistentFunction",
914
+ severity: "critical",
915
+ message: existsInProject ?
916
+ `Function '${used.name}' exists in your project but is not imported in this file`
917
+ : `Function '${used.name}' does not exist in project`,
918
+ line: used.line,
919
+ file: filePath,
920
+ code: used.code,
921
+ suggestion,
922
+ confidence,
923
+ reasoning,
924
+ });
925
+ }
926
+ else if (func &&
927
+ used.argCount !== undefined &&
928
+ func.paramCount !== undefined) {
929
+ if (used.argCount !== func.paramCount && strictMode) {
930
+ const { confidence, reasoning } = calculateConfidence({
931
+ issueType: "wrongParamCount",
932
+ symbolName: used.name,
933
+ similarSymbols: [],
934
+ existsInProject: true,
935
+ strictMode,
936
+ });
937
+ issues.push({
938
+ type: "wrongParamCount",
939
+ severity: "high",
940
+ message: `Function '${used.name}' expects ${func.paramCount} args, got ${used.argCount}`,
941
+ line: used.line,
942
+ file: filePath,
943
+ code: used.code,
944
+ suggestion: func.params ?
945
+ `Expected params: ${func.params.join(", ")}`
946
+ : `Check the function signature in ${func.file}`,
947
+ confidence,
948
+ reasoning,
949
+ });
950
+ }
951
+ }
952
+ }
953
+ else if (used.type === "methodCall") {
954
+ // 0. Skip whitelisted objects and chains (e.g., 'this.*', 'window.*', 'z.*', 'smthRef.current.*')
955
+ // Extract the root object, handling complex expressions like:
956
+ // - obj.property -> obj
957
+ // - obj?.property -> obj (optional chaining)
958
+ // - obj.method() -> obj
959
+ // - arr[index] -> arr
960
+ // - arr[index].property -> arr
961
+ // - fn().property -> fn
962
+ // - (expr as Type).property -> expr
963
+ function extractRootObject(obj) {
964
+ if (!obj)
965
+ return "";
966
+ // Handle parenthesized expressions at the start: (expr).prop -> extract from expr
967
+ if (obj.startsWith("(")) {
968
+ // Find the matching closing parenthesis
969
+ let depth = 1;
970
+ let i = 1;
971
+ while (i < obj.length && depth > 0) {
972
+ if (obj[i] === "(")
973
+ depth++;
974
+ else if (obj[i] === ")")
975
+ depth--;
976
+ i++;
977
+ }
978
+ if (depth === 0) {
979
+ // Extract the content inside the outermost parentheses
980
+ const innerExpr = obj.slice(1, i - 1).trim();
981
+ // Check if it's a type assertion (expr as Type) or (expr satisfies Type)
982
+ const asMatch = innerExpr.match(/^(.+?)\s+as\s+.+$/s);
983
+ if (asMatch) {
984
+ return extractRootObject(asMatch[1].trim());
985
+ }
986
+ const satisfiesMatch = innerExpr.match(/^(.+?)\s+satisfies\s+.+$/s);
987
+ if (satisfiesMatch) {
988
+ return extractRootObject(satisfiesMatch[1].trim());
989
+ }
990
+ // Not a type assertion — check if it's an arithmetic/logical/nullish expression
991
+ // e.g., (i + 1), (value / 1000), (milestones || []), (budgetUsedPercentage || 0)
992
+ // These are NOT object references — skip method validation entirely.
993
+ if (/[+\-*/%|&<>=!~^]/.test(innerExpr) || /\s\|\|\s/.test(innerExpr) || /\s\?\?\s/.test(innerExpr)) {
994
+ return ""; // Not a valid object reference
995
+ }
996
+ // Otherwise extract from the inner expression (e.g., (myObj).method())
997
+ return extractRootObject(innerExpr);
998
+ }
999
+ }
1000
+ // Split on delimiters and take the first part
1001
+ const root = obj
1002
+ .split("?.")[0]
1003
+ .split(".")[0]
1004
+ .split("[")[0]
1005
+ .split("(")[0]
1006
+ .trim();
1007
+ // If the extracted root contains spaces or operators, it's an expression, not an identifier
1008
+ // e.g., "i + 1" from a failed parenthesized expression extraction
1009
+ if (/\s/.test(root) || /[+\-*/%|&<>=!~^]/.test(root)) {
1010
+ return ""; // Not a valid identifier
1011
+ }
1012
+ return root;
1013
+ }
1014
+ const rootObject = extractRootObject(used.object || "");
1015
+ // If we can't reliably identify a root identifier (e.g. `[1,2,3].map()`),
1016
+ // skip method validation to avoid false positives.
1017
+ if (!rootObject) {
1018
+ continue;
1019
+ }
1020
+ // Heuristic: if an internal import is used as a lowerCamelCase instance name,
1021
+ // allow matching against a PascalCase class name (logger -> Logger).
1022
+ const inferredClassName = (() => {
1023
+ const first = rootObject[0];
1024
+ if (!first || first.toUpperCase() === first)
1025
+ return "";
1026
+ const candidate = first.toUpperCase() + rootObject.slice(1);
1027
+ return projectClasses.has(candidate) || hasContextClass(candidate) ? candidate : "";
1028
+ })();
1029
+ const scopesToMatch = new Set([used.object, rootObject, inferredClassName].filter(Boolean));
1030
+ // CRITICAL FIX: Skip ALL 'this'/'self'/'cls' method calls - we can't validate class scope
1031
+ // The 'this' keyword (JS/TS) and 'self'/'cls' (Python) are always in scope within a class context
1032
+ // TypeScript/ESLint/mypy handle class method validation, not CodeGuardian
1033
+ if (used.object === "this" || used.object?.startsWith("this.") ||
1034
+ used.object === "self" || used.object?.startsWith("self.") ||
1035
+ used.object === "cls" || used.object?.startsWith("cls.")) {
1036
+ continue; // Trust class scope - this is not a hallucination risk
1037
+ }
1038
+ // Skip other whitelisted global objects and common patterns
1039
+ if (used.object?.includes(".current") ||
1040
+ (rootObject &&
1041
+ [
1042
+ // JavaScript/TypeScript globals
1043
+ "window",
1044
+ "navigator",
1045
+ "document",
1046
+ "location",
1047
+ "history",
1048
+ "localStorage",
1049
+ "sessionStorage",
1050
+ "console",
1051
+ "process",
1052
+ "global",
1053
+ "globalThis",
1054
+ "Intl",
1055
+ "z",
1056
+ "t",
1057
+ "db",
1058
+ "prisma",
1059
+ "ctx",
1060
+ "supabase",
1061
+ "api",
1062
+ "client",
1063
+ "auth",
1064
+ // Python standard library modules (commonly used as objects for method calls)
1065
+ "os",
1066
+ "sys",
1067
+ "json",
1068
+ "re",
1069
+ "math",
1070
+ "logging",
1071
+ "datetime",
1072
+ "time",
1073
+ "pathlib",
1074
+ "collections",
1075
+ "itertools",
1076
+ "functools",
1077
+ "typing",
1078
+ "abc",
1079
+ "io",
1080
+ "hashlib",
1081
+ "hmac",
1082
+ "secrets",
1083
+ "base64",
1084
+ "urllib",
1085
+ "http",
1086
+ "email",
1087
+ "html",
1088
+ "xml",
1089
+ "csv",
1090
+ "sqlite3",
1091
+ "subprocess",
1092
+ "threading",
1093
+ "asyncio",
1094
+ "uuid",
1095
+ "copy",
1096
+ "shutil",
1097
+ "tempfile",
1098
+ "glob",
1099
+ "fnmatch",
1100
+ "pickle",
1101
+ "struct",
1102
+ "traceback",
1103
+ "inspect",
1104
+ "importlib",
1105
+ "contextlib",
1106
+ "dataclasses",
1107
+ "enum",
1108
+ "string",
1109
+ "textwrap",
1110
+ "random",
1111
+ "statistics",
1112
+ "decimal",
1113
+ "fractions",
1114
+ "operator",
1115
+ "warnings",
1116
+ "unittest",
1117
+ "pytest",
1118
+ "pprint",
1119
+ // Python common third-party module objects
1120
+ "logger",
1121
+ "app",
1122
+ "request",
1123
+ "response",
1124
+ "session",
1125
+ "cursor",
1126
+ "conn",
1127
+ "connection",
1128
+ "engine",
1129
+ "metadata",
1130
+ "router",
1131
+ "schema",
1132
+ "serializer",
1133
+ "queryset",
1134
+ "manager",
1135
+ "admin",
1136
+ "signals",
1137
+ "celery",
1138
+ "redis",
1139
+ "cache",
1140
+ "config",
1141
+ "settings",
1142
+ "flask",
1143
+ "django",
1144
+ "fastapi",
1145
+ "sqlalchemy",
1146
+ "pydantic",
1147
+ "httpx",
1148
+ "requests",
1149
+ "aiohttp",
1150
+ "np",
1151
+ "pd",
1152
+ "plt",
1153
+ "tf",
1154
+ "torch",
1155
+ "sk",
1156
+ ].includes(rootObject))) {
1157
+ // Special Case: Still validate the method name itself if it's NOT a known builtin
1158
+ // This ensures we catch true hallucinations like toast.hallucinatedMethod()
1159
+ if (isJSBuiltin(used.name) || isPythonBuiltin(used.name)) {
1160
+ continue;
1161
+ }
1162
+ // STEALTH HALLUCINATION DETECTION:
1163
+ // If the code casts a well-known global object to `any`/`unknown` and calls a
1164
+ // non-standard method, this is a high-confidence hallucination pattern.
1165
+ // e.g., `(window as any).applyMolecularStability?.()`
1166
+ // The `as any` cast is a deliberate type-safety bypass — the developer (or AI)
1167
+ // knows this method doesn't exist on the type. Combined with a non-standard
1168
+ // method name on a well-known global, this is a strong hallucination signal.
1169
+ //
1170
+ // We only flag this for true browser/Node globals (window, document, etc.),
1171
+ // NOT for generic whitelisted objects (db, prisma, etc.) where `as any` may
1172
+ // be a legitimate workaround for missing type definitions.
1173
+ const STEALTH_HALLUCINATION_GLOBALS = new Set([
1174
+ "window", "navigator", "document", "location", "history",
1175
+ "localStorage", "sessionStorage", "console", "process",
1176
+ "global", "globalThis", "Intl",
1177
+ ]);
1178
+ if (STEALTH_HALLUCINATION_GLOBALS.has(rootObject)) {
1179
+ const codeLine = used.code || "";
1180
+ if (codeLine.includes("as any") || codeLine.includes("as unknown")) {
1181
+ issues.push({
1182
+ type: "nonExistentMethod",
1183
+ severity: "critical",
1184
+ message: `Stealth hallucination: '${used.name}' does not exist on '${rootObject}' (called via unsafe 'as any' cast)`,
1185
+ line: used.line,
1186
+ file: filePath,
1187
+ code: used.code,
1188
+ suggestion: `Remove the hallucinated method call. '${rootObject}' does not have a '${used.name}' method. The 'as any' cast was used to bypass type checking.`,
1189
+ confidence: 96,
1190
+ reasoning: `Builtin global '${rootObject}' is cast to 'any'/'unknown' to call non-existent method '${used.name}'. This type-safety bypass combined with a non-standard method name is a strong indicator of AI hallucination.`,
1191
+ });
1192
+ continue;
1193
+ }
1194
+ }
1195
+ // If the method is NOT whitelisted, we still proceed to check if it exists in the project
1196
+ // but we've already validated the object.
1197
+ }
1198
+ // 1. Check contextual naming patterns first (e.preventDefault(), req.body, etc.)
1199
+ if (isContextuallyValid(used)) {
1200
+ continue; // Trust the vibe - this is a standard pattern
1201
+ }
1202
+ // SMART TEST RELAXATION:
1203
+ // Skip method checks in test files (mocks/spies often have magic methods)
1204
+ if (isTestFile(filePath) && !strictMode) {
1205
+ continue;
1206
+ }
1207
+ // 3. Check if the object itself exists
1208
+ const objExists = validClasses.has(rootObject) ||
1209
+ validVariables.has(rootObject) ||
1210
+ validFunctions.has(rootObject) ||
1211
+ isJSBuiltin(rootObject) ||
1212
+ (language === "python" && isPythonBuiltin(rootObject)) ||
1213
+ // Check if the object is an imported name (including failed imports — avoids double-flagging)
1214
+ allImportedNames.has(rootObject) ||
1215
+ // Check if the object is a locally-defined variable (function params, assignments, loop vars, etc.)
1216
+ localDefinitions.has(rootObject) ||
1217
+ // Python: Check if rootObject is the base of a dotted import (e.g., `import concurrent.futures` → `concurrent`)
1218
+ (language === "python" && imports.some(imp => imp.names.some(n => n.local.startsWith(rootObject + ".")))) ||
1219
+ // Always trust common short variable names in non-strict mode
1220
+ (!strictMode &&
1221
+ [
1222
+ // JS/TS common
1223
+ "z",
1224
+ "t",
1225
+ "db",
1226
+ "prisma",
1227
+ "ctx",
1228
+ "req",
1229
+ "res",
1230
+ "e",
1231
+ "i",
1232
+ // Python common
1233
+ "self",
1234
+ "cls",
1235
+ "logger",
1236
+ "app",
1237
+ "session",
1238
+ "cursor",
1239
+ "conn",
1240
+ "connection",
1241
+ "engine",
1242
+ "router",
1243
+ "config",
1244
+ "settings",
1245
+ "request",
1246
+ "response",
1247
+ "client",
1248
+ "server",
1249
+ "cache",
1250
+ "registry",
1251
+ "factory",
1252
+ "builder",
1253
+ "handler",
1254
+ "manager",
1255
+ "service",
1256
+ "controller",
1257
+ "serializer",
1258
+ "validator",
1259
+ "middleware",
1260
+ "schema",
1261
+ "model",
1262
+ "form",
1263
+ "view",
1264
+ "template",
1265
+ "context",
1266
+ "fixture",
1267
+ "mock",
1268
+ "patch",
1269
+ "monkeypatch",
1270
+ ].includes(rootObject));
1271
+ // If the object doesn't exist at all, flag it as a hallucination
1272
+ if (!objExists) {
1273
+ // Use rootObject for checking (handles complex expressions like arr[index].method())
1274
+ const objectToCheck = rootObject || used.object;
1275
+ // ... (existing undefinedVariable check) ...
1276
+ const suggestion = suggestSimilar(objectToCheck, [
1277
+ ...validClasses.keys(),
1278
+ ...validVariables.keys(),
1279
+ ...validFunctions.keys(),
1280
+ ]);
1281
+ const similarSymbols = extractSimilarSymbols(suggestion);
1282
+ const { confidence, reasoning } = calculateConfidence({
1283
+ issueType: "undefinedVariable",
1284
+ symbolName: objectToCheck,
1285
+ similarSymbols,
1286
+ existsInProject: projectClasses.has(objectToCheck) ||
1287
+ projectVariables.has(objectToCheck) ||
1288
+ projectFunctions.has(objectToCheck),
1289
+ strictMode,
1290
+ });
1291
+ issues.push({
1292
+ type: "undefinedVariable",
1293
+ severity: "critical",
1294
+ message: `Object '${objectToCheck}' is not defined or imported (used in ${used.object}.${used.name}())`,
1295
+ line: used.line,
1296
+ file: filePath,
1297
+ code: used.code,
1298
+ suggestion,
1299
+ confidence,
1300
+ reasoning,
1301
+ });
1302
+ continue; // Skip method validation if object doesn't exist
1303
+ }
1304
+ // Skip standard built-in methods (map, toString, read, etc.) only AFTER we've
1305
+ // confirmed the object exists. This preserves hallucination detection for
1306
+ // unknown objects calling common method names (e.g., AITaskPredictor.connect()).
1307
+ if (((language === "javascript" || language === "typescript") &&
1308
+ isJSBuiltin(used.name)) ||
1309
+ (language === "python" && isPythonBuiltin(used.name))) {
1310
+ continue;
1311
+ }
1312
+ // 3. Determine if we should check the method call itself
1313
+ // In strict mode, we check everything.
1314
+ // In auto mode, we check:
1315
+ // - Imports from missing/hallucinated packages (we know the package is gone)
1316
+ // - Internal imports where we have class/method information
1317
+ let shouldCheck = strictMode;
1318
+ if (!shouldCheck) {
1319
+ const objectName = rootObject || used.object;
1320
+ const imp = imports.find((i) => i.names.some((n) => n.local === objectName || n.local === used.object));
1321
+ if (imp) {
1322
+ if (missingPackages.has(imp.module)) {
1323
+ shouldCheck = true; // Hallucinated import - definitely flag usages!
1324
+ }
1325
+ else if (!imp.isExternal) {
1326
+ // For internal imports, check if we have CLASS info
1327
+ const objClass = projectClasses.get(objectName) ||
1328
+ (inferredClassName ? projectClasses.get(inferredClassName) : undefined);
1329
+ if (objClass || inferredClassName) {
1330
+ shouldCheck = true; // We have class info, so we can validate methods
1331
+ }
1332
+ // Also check if we have scoped method info for this object
1333
+ // This handles const object literals with known methods:
1334
+ // export const projectsApi = { getProjects: async () => {...}, ... }
1335
+ // The AST extractor captures these as methods with scope="projectsApi",
1336
+ // so we know the exact shape and can validate method calls.
1337
+ // This is different from plain key objects (contractKeys.all) which
1338
+ // have no methods in the symbol table.
1339
+ if (!shouldCheck) {
1340
+ for (const [, sym] of validMethods) {
1341
+ if (sym.scope === objectName) {
1342
+ shouldCheck = true;
1343
+ break;
1344
+ }
1345
+ }
1346
+ }
1347
+ // Also check validFunctions — object literal methods may be stored
1348
+ // as kind=function (not method), landing in validFunctions instead
1349
+ if (!shouldCheck) {
1350
+ for (const [, sym] of validFunctions) {
1351
+ if (sym.scope === objectName) {
1352
+ shouldCheck = true;
1353
+ break;
1354
+ }
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ else {
1360
+ // Objects not from imports (locally defined)
1361
+ // ONLY check if we have class info, otherwise we don't know the type enough to flag it
1362
+ // Use projectClasses (actual class definitions), not validClasses (which includes all imports)
1363
+ const objClass = projectClasses.get(objectName) ||
1364
+ (inferredClassName ? projectClasses.get(inferredClassName) : undefined);
1365
+ if ((objClass || inferredClassName) && objClass?.file !== "(new code)") {
1366
+ shouldCheck = true;
1367
+ }
1368
+ // Local literal inference: method calls on obvious built-in types (e.g., array literals)
1369
+ // can be validated without introducing false positives on unknown call results.
1370
+ if (!shouldCheck && objectName) {
1371
+ const kind = localLiteralKinds.get(objectName);
1372
+ if (kind === "array" || kind === "string") {
1373
+ shouldCheck = true;
1374
+ }
1375
+ }
1376
+ }
1377
+ }
1378
+ // console.log(`DEBUG: Method ${used.object}.${used.name} - shouldCheck: ${shouldCheck}`);
1379
+ if (!shouldCheck)
1380
+ continue;
1381
+ // Look up method by name, checking ALL symbols with that name (not just the first)
1382
+ // This handles multiple services with same-named methods (e.g., getProject on both
1383
+ // clientPortalService and projectsApi)
1384
+ const contextSyms = getContextSymbolsByName(used.name);
1385
+ const contextMethodSyms = contextSyms.filter((s) => s.type === "method");
1386
+ const contextFuncSyms = contextSyms.filter((s) => s.type === "function");
1387
+ const methodSyms = [
1388
+ ...(projectMethods.get(used.name) || []),
1389
+ ...contextMethodSyms,
1390
+ ];
1391
+ const funcSyms = [
1392
+ ...(projectFunctions.get(used.name) || []),
1393
+ ...contextFuncSyms,
1394
+ ];
1395
+ const methodSym = validMethods.get(used.name);
1396
+ const funcSym = validFunctions.get(used.name);
1397
+ // Check ALL method symbols for a scope match, not just the single representative
1398
+ const methodMatches = methodSyms.some((s) => !s.scope || scopesToMatch.has(s.scope)) ||
1399
+ (methodSym && (!methodSym.scope || scopesToMatch.has(methodSym.scope)));
1400
+ // Check ALL function symbols for a scope match
1401
+ // Local variables (from "(new code)") like `const stream = ...` should NOT
1402
+ // validate `ApiService.stream()` — that's a name collision, not a real method.
1403
+ const funcMatches = funcSyms.some((s) => (!s.scope || scopesToMatch.has(s.scope)) && s.file !== "(new code)") ||
1404
+ (funcSym &&
1405
+ (!funcSym.scope || scopesToMatch.has(funcSym.scope)) &&
1406
+ funcSym.file !== "(new code)");
1407
+ // Skip well-known inherited methods from common frameworks
1408
+ // These methods exist on base classes (Pydantic BaseModel, SQLAlchemy Model, etc.)
1409
+ // and won't appear in the project symbol table
1410
+ const FRAMEWORK_METHODS = new Set([
1411
+ // Pydantic BaseModel methods (v1 + v2)
1412
+ "model_validate", "model_dump", "model_json_schema", "model_copy",
1413
+ "model_validate_json", "model_dump_json", "model_fields_set",
1414
+ "model_construct", "model_post_init", "model_rebuild",
1415
+ "dict", "json", "parse_obj", "parse_raw", "parse_file",
1416
+ "from_orm", "schema", "schema_json", "validate", "update_forward_refs",
1417
+ "copy", "construct",
1418
+ // SQLAlchemy Model/Query methods
1419
+ "query", "filter", "filter_by", "all", "first", "one", "one_or_none",
1420
+ "get", "count", "delete", "update", "order_by", "limit", "offset",
1421
+ "join", "outerjoin", "group_by", "having", "distinct", "subquery",
1422
+ "scalar", "scalars", "execute", "add", "flush", "commit", "rollback",
1423
+ "refresh", "expire", "expunge", "merge", "close",
1424
+ // SQLAlchemy Column expression methods (called on Model.column attributes)
1425
+ "desc", "asc", "in_", "notin_", "not_in", "isnot", "is_", "is_not",
1426
+ "like", "ilike", "not_like", "not_ilike", "contains", "startswith", "endswith",
1427
+ "between", "any_", "has", "label", "cast", "op", "collate",
1428
+ "nullsfirst", "nullslast", "regexp_match", "regexp_replace",
1429
+ "concat", "distinct", "nulls_first", "nulls_last",
1430
+ // Django ORM methods
1431
+ "objects", "create", "get_or_create", "update_or_create",
1432
+ "bulk_create", "bulk_update", "values", "values_list",
1433
+ "annotate", "aggregate", "exists", "exclude", "select_related",
1434
+ "prefetch_related", "defer", "only", "using", "raw",
1435
+ "save", "full_clean", "clean", "clean_fields",
1436
+ ]);
1437
+ if (FRAMEWORK_METHODS.has(used.name))
1438
+ continue;
1439
+ // Python-specific: Skip common dynamic method patterns that can't be resolved
1440
+ // by static analysis due to Python's dynamic typing.
1441
+ // In Python, `client` could be httpx.AsyncClient, requests.Session, supabase.Client, etc.
1442
+ // We can't determine the runtime type, so we whitelist common method names that
1443
+ // appear on well-known library objects.
1444
+ if (language === "python") {
1445
+ const PYTHON_DYNAMIC_METHODS = new Set([
1446
+ // HTTP client methods (httpx, requests, aiohttp, etc.)
1447
+ "post", "put", "patch", "request", "head", "options",
1448
+ // Python string methods called on model attribute chains (e.g., user.email.split())
1449
+ "split", "strip", "lstrip", "rstrip", "lower", "upper",
1450
+ "replace", "encode", "decode", "format", "capitalize",
1451
+ // Storage/external client methods (Supabase, S3, GCS, etc.)
1452
+ "from_", "upload", "download", "create_signed_url", "get_public_url",
1453
+ "get_bucket", "create_bucket", "remove", "list",
1454
+ // Redis client methods
1455
+ "setex", "setnx", "getex", "hset", "hget", "hdel", "hgetall",
1456
+ "lpush", "rpush", "lpop", "rpop", "lrange", "sadd", "srem", "smembers",
1457
+ "zadd", "zrem", "zrange", "zrangebyscore", "expire", "ttl", "pttl",
1458
+ "publish", "subscribe", "unsubscribe", "pipeline",
1459
+ // Webhook/integration client methods
1460
+ "create_webhook", "delete_webhook", "grant_access", "revoke_access",
1461
+ "get_commit_details", "get_commits", "get_branches",
1462
+ // Task/job object attribute access
1463
+ "func", "args", "kwargs", "result", "status",
1464
+ // Celery task methods
1465
+ "delay", "apply_async", "retry", "revoke",
1466
+ ]);
1467
+ if (PYTHON_DYNAMIC_METHODS.has(used.name))
1468
+ continue;
1469
+ }
1470
+ if (!methodMatches && !funcMatches) {
1471
+ // Build list of valid methods for this object type
1472
+ const objectMethods = [];
1473
+ for (const [name, sym] of validMethods) {
1474
+ // Include methods that either have no scope (general) or match the object
1475
+ if (!sym.scope || scopesToMatch.has(sym.scope)) {
1476
+ objectMethods.push(name);
1477
+ }
1478
+ }
1479
+ const suggestion = suggestSimilar(used.name, objectMethods);
1480
+ const similarSymbols = extractSimilarSymbols(suggestion);
1481
+ const { confidence, reasoning } = calculateConfidence({
1482
+ issueType: "nonExistentMethod",
1483
+ symbolName: used.name,
1484
+ similarSymbols,
1485
+ existsInProject: false,
1486
+ strictMode,
1487
+ });
1488
+ issues.push({
1489
+ type: "nonExistentMethod",
1490
+ severity: "medium",
1491
+ message: `Method '${used.name}' not found on '${used.object}' (verify manually)`,
1492
+ line: used.line,
1493
+ file: filePath,
1494
+ code: used.code,
1495
+ suggestion,
1496
+ confidence,
1497
+ reasoning,
1498
+ });
1499
+ }
1500
+ }
1501
+ else if (used.type === "instantiation") {
1502
+ if (!validClasses.has(used.name)) {
1503
+ // Skip imported names — they are validated separately
1504
+ if (allImportedNames.has(used.name))
1505
+ continue;
1506
+ // Skip locally-defined variables (e.g., const ParserClass = require('pdf-parse'))
1507
+ if (localDefinitions.has(used.name))
1508
+ continue;
1509
+ // Skip if it exists as a variable or function (dynamic class assignment)
1510
+ if (validVariables.has(used.name) || validFunctions.has(used.name))
1511
+ continue;
1512
+ // Built-in check (Tier 1.5)
1513
+ if ((language === "python" && isPythonBuiltin(used.name)) ||
1514
+ ((language === "javascript" || language === "typescript") &&
1515
+ isJSBuiltin(used.name))) {
1516
+ continue;
1517
+ }
1518
+ // SMART TEST RELAXATION:
1519
+ if (isTestFile(filePath) && !strictMode) {
1520
+ continue;
1521
+ }
1522
+ const existsInProject = projectClasses.has(used.name);
1523
+ let suggestion = "";
1524
+ if (existsInProject) {
1525
+ const sym = projectClasses.get(used.name);
1526
+ if (sym) {
1527
+ suggestion = `Add: import { ${used.name} } from './${sym.file.replace(/\\/g, "/")}'`;
1528
+ }
1529
+ else {
1530
+ suggestion = `Add: import { ${used.name} } from '...'`;
1531
+ }
1532
+ }
1533
+ else {
1534
+ suggestion = suggestSimilar(used.name, Array.from(projectClasses.keys()));
1535
+ }
1536
+ const similarSymbols = extractSimilarSymbols(suggestion);
1537
+ const { confidence, reasoning } = calculateConfidence({
1538
+ issueType: "nonExistentClass",
1539
+ symbolName: used.name,
1540
+ similarSymbols,
1541
+ existsInProject,
1542
+ strictMode,
1543
+ });
1544
+ issues.push({
1545
+ type: "nonExistentClass",
1546
+ severity: "critical",
1547
+ message: existsInProject ?
1548
+ `Class '${used.name}' exists in your project but is not imported in this file`
1549
+ : `Class '${used.name}' does not exist in project`,
1550
+ line: used.line,
1551
+ file: filePath,
1552
+ code: used.code,
1553
+ suggestion,
1554
+ confidence,
1555
+ reasoning,
1556
+ });
1557
+ }
1558
+ }
1559
+ else if (used.type === "reference") {
1560
+ // CRITICAL FIX: Skip property access on 'this'/'self'/'cls' (e.g., this.ws, self.data, cls._client)
1561
+ // These are class properties and should not be validated as standalone variables
1562
+ if (used.object === "this" || used.object?.startsWith("this.") ||
1563
+ used.object === "self" || used.object?.startsWith("self.") ||
1564
+ used.object === "cls" || used.object?.startsWith("cls.")) {
1565
+ continue; // Trust class scope - properties are validated by TypeScript/mypy
1566
+ }
1567
+ const func = validFunctions.get(used.name);
1568
+ const cls = validClasses.get(used.name);
1569
+ const variable = validVariables.get(used.name);
1570
+ if (!func && !cls && !variable) {
1571
+ // Skip imported names — they are validated separately via import checks
1572
+ if (allImportedNames.has(used.name))
1573
+ continue;
1574
+ // Skip locally-defined variables (function params, assignments, loop vars, etc.)
1575
+ if (localDefinitions.has(used.name))
1576
+ continue;
1577
+ // Built-in check (Tier 1.5)
1578
+ if ((language === "python" && isPythonBuiltin(used.name)) ||
1579
+ ((language === "javascript" || language === "typescript") &&
1580
+ (isJSBuiltin(used.name) ||
1581
+ isTSBuiltinType(used.name) ||
1582
+ [
1583
+ "this",
1584
+ "props",
1585
+ "state",
1586
+ "window",
1587
+ "navigator",
1588
+ "document",
1589
+ "location",
1590
+ "Intl",
1591
+ "JSON",
1592
+ "Math",
1593
+ "Date",
1594
+ "Array",
1595
+ "Object",
1596
+ "String",
1597
+ "Number",
1598
+ "Boolean",
1599
+ "Promise",
1600
+ "Error",
1601
+ "process",
1602
+ "global",
1603
+ "globalThis",
1604
+ "self",
1605
+ ].includes(used.name)))) {
1606
+ continue;
1607
+ }
1608
+ // SMART TEST RELAXATION:
1609
+ if (isTestFile(filePath) && !strictMode) {
1610
+ continue;
1611
+ }
1612
+ const existsInProject = projectFunctions.has(used.name) ||
1613
+ projectClasses.has(used.name) ||
1614
+ projectVariables.has(used.name);
1615
+ let suggestion = "";
1616
+ if (existsInProject) {
1617
+ const funcSymsForRef = projectFunctions.get(used.name);
1618
+ const sym = (funcSymsForRef && funcSymsForRef[0]) ||
1619
+ projectClasses.get(used.name) ||
1620
+ projectVariables.get(used.name);
1621
+ if (sym) {
1622
+ suggestion = `Add: import { ${used.name} } from './${sym.file.replace(/\\/g, "/")}'`;
1623
+ }
1624
+ else {
1625
+ suggestion = `Add: import { ${used.name} } from '...'`;
1626
+ }
1627
+ }
1628
+ else {
1629
+ suggestion = suggestSimilar(used.name, [
1630
+ ...projectFunctions.keys(),
1631
+ ...projectClasses.keys(),
1632
+ ...projectVariables.keys(),
1633
+ ]);
1634
+ }
1635
+ const similarSymbols = extractSimilarSymbols(suggestion);
1636
+ const { confidence, reasoning } = calculateConfidence({
1637
+ issueType: "undefinedVariable",
1638
+ symbolName: used.name,
1639
+ similarSymbols,
1640
+ existsInProject,
1641
+ strictMode,
1642
+ });
1643
+ issues.push({
1644
+ type: "undefinedVariable",
1645
+ severity: "critical",
1646
+ message: existsInProject ?
1647
+ `Variable '${used.name}' exists in your project but is not imported in this file`
1648
+ : `Variable '${used.name}' is not defined or imported`,
1649
+ line: used.line,
1650
+ file: filePath,
1651
+ code: used.code,
1652
+ suggestion,
1653
+ confidence,
1654
+ reasoning,
1655
+ });
1656
+ }
1657
+ }
1658
+ }
1659
+ // Tier 3: Unused Import Detection
1660
+ const usedNames = new Set(usedSymbols.map((u) => u.name));
1661
+ // Also include object names from method calls (e.g., 'logger' in 'logger.info()')
1662
+ for (const used of usedSymbols) {
1663
+ if (used.type === "methodCall" && used.object) {
1664
+ usedNames.add(used.object);
1665
+ }
1666
+ }
1667
+ // Include type references (e.g., function(req: Request))
1668
+ for (const typeRef of typeReferences) {
1669
+ usedNames.add(typeRef.name);
1670
+ }
1671
+ // For Python __init__.py files, all imports are re-exports — skip unused detection
1672
+ const isInitPy = language === "python" && filePath.endsWith("__init__.py");
1673
+ // Also build a set of names in __all__ for the current file's module
1674
+ let currentModuleAllExports = null;
1675
+ if (language === "python" && filePath && pythonExports.size > 0) {
1676
+ const basePath = context?.projectPath || "";
1677
+ const relPath = filePath.startsWith(basePath) ? filePath.slice(basePath.length + 1) : filePath;
1678
+ const pyModPath = relPath.replace(/__init__\.py$/, "").replace(/\.py$/, "").replace(/\//g, ".").replace(/\.$/, "");
1679
+ currentModuleAllExports = pythonExports.get(pyModPath) || null;
1680
+ }
1681
+ // For Python: Build a set of symbol names that appear in the code text as word boundaries
1682
+ // This catches usages in type annotations, decorators, attribute access, etc. that the
1683
+ // AST-based extractor might miss (e.g., `def foo(x: Optional[str])`, `logging.DEBUG`)
1684
+ let codeWordSet = null;
1685
+ if (language === "python") {
1686
+ codeWordSet = new Set();
1687
+ // Extract all word-like tokens from the code (excluding comment/string lines for accuracy)
1688
+ const lines = newCode.split("\n");
1689
+ for (let i = 0; i < lines.length; i++) {
1690
+ const line = lines[i].trim();
1691
+ // Skip pure comment lines (but still scan lines with inline code + comments)
1692
+ if (line.startsWith("#"))
1693
+ continue;
1694
+ // Extract all identifiers from the line
1695
+ const words = line.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g);
1696
+ if (words) {
1697
+ for (const w of words)
1698
+ codeWordSet.add(w);
1699
+ }
1700
+ }
1701
+ }
1702
+ for (const imp of imports) {
1703
+ for (const name of imp.names) {
1704
+ if (name.local === "*" || name.imported === "*")
1705
+ continue; // Skip wildcards
1706
+ if (name.imported.startsWith("React"))
1707
+ continue; // Skip React imports
1708
+ // Skip unused import checks for Python __init__.py re-export files
1709
+ if (isInitPy)
1710
+ continue;
1711
+ // Skip if the import is listed in the module's __all__ (it's a re-export)
1712
+ if (currentModuleAllExports && currentModuleAllExports.has(name.local))
1713
+ continue;
1714
+ if (!usedNames.has(name.local)) {
1715
+ // Python fallback: Check if any non-import line in the code contains the symbol name
1716
+ // This catches type annotations, decorators, attribute access, etc. that AST misses
1717
+ if (language === "python" && codeWordSet) {
1718
+ // The symbol must appear in the code AND not only on import lines
1719
+ if (codeWordSet.has(name.local)) {
1720
+ // Verify it appears on at least one non-import line
1721
+ const importLineText = getLineFromCode(newCode, imp.line);
1722
+ const appearsElsewhere = newCode.split("\n").some((line, idx) => {
1723
+ if (idx + 1 === imp.line)
1724
+ return false; // Skip the import line itself
1725
+ const trimmed = line.trim();
1726
+ if (trimmed.startsWith("#"))
1727
+ return false; // Skip comments
1728
+ if (trimmed.startsWith("import ") || trimmed.startsWith("from "))
1729
+ return false; // Skip other imports
1730
+ return new RegExp(`\\b${name.local.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(line);
1731
+ });
1732
+ if (appearsElsewhere)
1733
+ continue; // Used in code, skip the unused import warning
1734
+ }
1735
+ }
1736
+ const { confidence, reasoning } = calculateConfidence({
1737
+ issueType: "unusedImport",
1738
+ symbolName: name.local,
1739
+ similarSymbols: [],
1740
+ existsInProject: true,
1741
+ strictMode,
1742
+ });
1743
+ issues.push({
1744
+ type: "unusedImport",
1745
+ severity: "warning",
1746
+ message: `Imported symbol '${name.local}' is never used`,
1747
+ line: imp.line,
1748
+ file: filePath,
1749
+ code: getLineFromCode(newCode, imp.line),
1750
+ suggestion: `Remove the unused import: ${name.local}`,
1751
+ confidence,
1752
+ reasoning,
1753
+ });
1754
+ }
1755
+ }
1756
+ }
1757
+ // Tier 3.5: Type-only import misuse detection
1758
+ // Check if something imported with "import type" is used as a value
1759
+ for (const imp of imports) {
1760
+ if (!imp.isTypeOnly)
1761
+ continue; // Only check type-only imports
1762
+ for (const name of imp.names) {
1763
+ // Check if this type-imported symbol is used as a value (not just in type positions)
1764
+ const usages = usedSymbols.filter(u => u.name === name.local);
1765
+ const typeUsages = typeReferences.filter(t => t.name === name.local);
1766
+ // If used in runtime contexts (call, instantiation, etc.) but imported as type
1767
+ const runtimeUsages = usages.filter(u => u.type === "call" ||
1768
+ u.type === "instantiation" ||
1769
+ u.type === "methodCall");
1770
+ if (runtimeUsages.length > 0) {
1771
+ issues.push({
1772
+ type: "typeOnlyImportMisuse",
1773
+ severity: "high",
1774
+ message: `'${name.local}' is imported as a type but used as a value at runtime`,
1775
+ line: runtimeUsages[0].line,
1776
+ file: filePath,
1777
+ code: getLineFromCode(newCode, runtimeUsages[0].line),
1778
+ suggestion: `Change to regular import: import { ${name.local} } from '${imp.module}'`,
1779
+ confidence: 95,
1780
+ reasoning: `Type-only imports are erased at compile time and cannot be used for runtime values like function calls or instantiation.`,
1781
+ });
1782
+ }
1783
+ }
1784
+ }
1785
+ return issues;
1786
+ }
1787
+ /**
1788
+ * Validate that usage of symbols follows established project patterns (Secret #5).
1789
+ * Detects "ritual" deviations such as missing co-occurring calls.
1790
+ *
1791
+ * @param usedSymbols - Symbols used in the new code
1792
+ * @param projectContext - Project context with symbol graph and patterns
1793
+ * @returns Array of pattern deviation issues
1794
+ */
1795
+ export function validateUsagePatterns(usedSymbols, projectContext, filePath = "") {
1796
+ const issues = [];
1797
+ if (!projectContext.symbolGraph)
1798
+ return issues;
1799
+ // Initialize analyzer if not already done for this graph
1800
+ // (In a real system, patterns would be cached/pre-computed)
1801
+ // usagePatternAnalyzer.analyze(projectContext.symbolGraph);
1802
+ const usedNames = usedSymbols.map((u) => u.name);
1803
+ for (const used of usedSymbols) {
1804
+ if (used.type !== "call")
1805
+ continue;
1806
+ const deviations = usagePatternAnalyzer.checkDeviations(used.name, usedNames);
1807
+ for (const msg of deviations) {
1808
+ issues.push({
1809
+ type: "architecturalDeviation",
1810
+ severity: "warning", // Patterns are suggestions, not necessarily hard errors
1811
+ message: msg,
1812
+ line: used.line,
1813
+ file: filePath || projectContext.projectPath, // Best effort for patterns
1814
+ code: used.code,
1815
+ suggestion: "Verify if this ritual call is required in your context.",
1816
+ confidence: 70,
1817
+ reasoning: `Learned from ${projectContext.totalFiles} files in your project that these symbols usually appear together.`,
1818
+ });
1819
+ }
1820
+ }
1821
+ return issues;
1822
+ }
1823
+ /**
1824
+ * Helper to identify test files.
1825
+ * Test files often contain "hallucinated" globals (describe, it, expect) and mocks.
1826
+ */
1827
+ function isTestFile(filePath) {
1828
+ if (!filePath)
1829
+ return false;
1830
+ const lower = filePath.toLowerCase();
1831
+ return (lower.includes(".test.") ||
1832
+ lower.includes(".spec.") ||
1833
+ lower.includes("/tests/") ||
1834
+ lower.includes("/__tests__/") ||
1835
+ lower.includes("/test-utils/"));
1836
+ }
1837
+ //# sourceMappingURL=validation.js.map