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,861 @@
1
+ /**
2
+ * Dead Code Analyzer Module
3
+ *
4
+ * This module detects unused exports, orphaned files, and unused functions in a codebase.
5
+ * It uses a multi-phase analysis approach:
6
+ * 1. Fast import graph check (O(1) lookup)
7
+ * 2. Selective deep AST analysis for potentially unused symbols
8
+ * 3. Timeout protection and batch processing for large codebases
9
+ *
10
+ * The analyzer maintains several caches for performance:
11
+ * - fileContentCache: Avoids re-reading files
12
+ * - typeReferencesCache: Avoids re-parsing AST for type references
13
+ * - symbolUsageCache: Avoids redundant symbol usage checks
14
+ *
15
+ * @format
16
+ */
17
+ import * as fs from "fs/promises";
18
+ import { resolveImport } from "../../context/projectContext.js";
19
+ import { extractImportsAST, extractUsagesAST, } from "./extractors/index.js";
20
+ import { logger } from "../../utils/logger.js";
21
+ import * as path from "path";
22
+ // @ts-ignore
23
+ import { minimatch as minimatchFunc } from "minimatch";
24
+ const minimatch = minimatchFunc;
25
+ // Import the AST-based unused locals detection
26
+ import { detectUnusedLocals, detectUnusedLocalsAST } from "./unusedLocals.js";
27
+ export { detectUnusedLocals, detectUnusedLocalsAST };
28
+ // ============================================================================
29
+ // Caches for Performance
30
+ // ============================================================================
31
+ // Cache for file contents to avoid re-reading
32
+ const fileContentCache = new Map();
33
+ // Cache for AST type references to avoid re-parsing
34
+ const typeReferencesCache = new Map();
35
+ // Cache for symbol usage results to avoid redundant checks
36
+ const symbolUsageCache = new Map();
37
+ // Cache for file modification times to enable incremental analysis
38
+ const fileMtimeCache = new Map();
39
+ // Cache for string literals per file
40
+ const fileStringsCache = new Map();
41
+ /**
42
+ * Check if a file has been modified since last scan
43
+ */
44
+ async function hasFileChanged(filePath) {
45
+ try {
46
+ const stats = await fs.stat(filePath);
47
+ const currentMtime = stats.mtimeMs;
48
+ const cachedMtime = fileMtimeCache.get(filePath);
49
+ if (cachedMtime === undefined || cachedMtime !== currentMtime) {
50
+ // File is new or has been modified
51
+ fileMtimeCache.set(filePath, currentMtime);
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ catch (err) {
57
+ // If we can't stat the file, assume it changed
58
+ return true;
59
+ }
60
+ }
61
+ // ============================================================================
62
+ // Helper Functions for Dead Code Detection
63
+ // ============================================================================
64
+ /**
65
+ * Check if a symbol is directly imported anywhere in the codebase.
66
+ *
67
+ * This is the fastest check - O(1) lookup in the import graph.
68
+ * If a symbol is imported, it's definitely being used.
69
+ *
70
+ * @param symbolName - Name of the symbol to check
71
+ * @param definedInFile - File path where the symbol is defined
72
+ * @param symbolsImportedFromFile - Map of file paths to sets of imported symbol names
73
+ * @returns true if the symbol is imported anywhere, false otherwise
74
+ */
75
+ export function isSymbolImported(symbolName, definedInFile, symbolsImportedFromFile) {
76
+ const importedSymbols = symbolsImportedFromFile.get(definedInFile);
77
+ return importedSymbols?.has(symbolName) ?? false;
78
+ }
79
+ /**
80
+ * Check if a symbol is referenced as a type anywhere in the codebase.
81
+ *
82
+ * Uses AST-based type reference extraction with pre-loaded cache.
83
+ * This is more expensive than import checking, so we limit the scope.
84
+ *
85
+ * Type references include:
86
+ * - Type annotations (: TypeName)
87
+ * - Generic parameters (<TypeName>)
88
+ * - Return types
89
+ * - Extends/implements clauses
90
+ * - Property types in interfaces
91
+ *
92
+ * @param symbolName - Name of the symbol to check
93
+ * @param definedInFile - File path where the symbol is defined
94
+ * @param context - Project context with file information
95
+ * @returns true if the symbol is referenced as a type anywhere, false otherwise
96
+ */
97
+ export async function isSymbolTypeReferenced(symbolName, definedInFile, context) {
98
+ // OPTIMIZED: Use symbolGraph for fast lookup (O(1))
99
+ if (context.symbolGraph) {
100
+ const usage = context.symbolGraph.usage.get(symbolName);
101
+ if (usage && usage.calledBy.size > 0) {
102
+ return true;
103
+ }
104
+ }
105
+ // Check if any file imports this symbol as a type
106
+ // This is faster than parsing AST for type references
107
+ for (const [filePath, fileInfo] of context.files) {
108
+ for (const imp of fileInfo.imports) {
109
+ if (imp.namedImports.includes(symbolName) || imp.defaultImport === symbolName) {
110
+ return true;
111
+ }
112
+ }
113
+ }
114
+ return false;
115
+ }
116
+ /**
117
+ * Check if a symbol is used in function calls or method calls via AST.
118
+ *
119
+ * This checks for runtime usage of symbols (not just type references).
120
+ * Uses cached file contents and limits scope for performance.
121
+ *
122
+ * @param symbolName - Name of the symbol to check
123
+ * @param context - Project context with file information
124
+ * @returns true if the symbol is called or instantiated anywhere, false otherwise
125
+ */
126
+ export async function isSymbolCalledOrInstantiated(symbolName, context, symbolScope) {
127
+ // Signal 1: Check the pre-computed symbol graph (Secret #3)
128
+ // This is extremely fast (O(1)) and accurate if the graph is AST-driven
129
+ if (context.symbolGraph) {
130
+ const usage = context.symbolGraph.usage.get(symbolName);
131
+ // Note: calledBy in symbolGraph includes files that call it
132
+ if (usage && usage.calledBy.size > 0) {
133
+ return true;
134
+ }
135
+ }
136
+ // Fallback: Selective file content check with regex pre-filter
137
+ // Instead of parsing full AST for up to 50 files per symbol (very expensive),
138
+ // use a fast regex pre-filter on cached file content to find candidate files,
139
+ // then only do AST analysis on files that actually contain the symbol name.
140
+ let checkedFiles = 0;
141
+ const MAX_FILES_TO_CHECK = 50; // Limit scope to prevent timeout
142
+ const symbolRegex = new RegExp(`\\b${escapeRegex(symbolName)}\\b`);
143
+ for (const [filePath, fileInfo] of context.files) {
144
+ if (checkedFiles >= MAX_FILES_TO_CHECK)
145
+ break;
146
+ // Get content from cache or read from disk
147
+ let content = fileContentCache.get(filePath);
148
+ if (!content) {
149
+ try {
150
+ content = await fs.readFile(filePath, "utf-8");
151
+ fileContentCache.set(filePath, content);
152
+ }
153
+ catch {
154
+ // Skip files we can't read
155
+ continue;
156
+ }
157
+ }
158
+ checkedFiles++;
159
+ // Fast pre-filter: skip files that don't even contain the symbol name
160
+ if (!symbolRegex.test(content))
161
+ continue;
162
+ // Only parse AST for files that actually reference the symbol
163
+ const lang = fileInfo.language === "javascript" ? "javascript" : "typescript";
164
+ const imports = extractImportsAST(content, lang);
165
+ const usages = extractUsagesAST(content, lang, imports);
166
+ // Check for direct function calls or instantiations
167
+ for (const usage of usages) {
168
+ if (usage.name === symbolName) {
169
+ // If symbol has a scope (e.g., it's a method of an object),
170
+ // verify that the usage is via that object
171
+ if (symbolScope && usage.type === "methodCall" && usage.object) {
172
+ if (usage.object === symbolScope) {
173
+ return true;
174
+ }
175
+ // Check if the object is imported from the file where symbol is defined
176
+ const fi = context.files.get(filePath);
177
+ if (fi) {
178
+ const allFileKeys = Array.from(context.files.keys());
179
+ for (const imp of fi.imports) {
180
+ const resolvedPath = resolveImport(imp.source, filePath, allFileKeys);
181
+ if (resolvedPath) {
182
+ const defFileInfo = context.files.get(resolvedPath);
183
+ if (defFileInfo) {
184
+ const matchingSymbol = defFileInfo.symbols.find(s => s.name === symbolName && s.scope === symbolScope && s.exported);
185
+ if (matchingSymbol) {
186
+ return true;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ else if (!symbolScope) {
194
+ // No scope required, any usage matches
195
+ return true;
196
+ }
197
+ }
198
+ // Check for method calls on objects (e.g., api.symbolName())
199
+ if (usage.type === "methodCall" && usage.name === symbolName && !symbolScope) {
200
+ return true;
201
+ }
202
+ }
203
+ }
204
+ return false;
205
+ }
206
+ /**
207
+ * Check if a symbol is used within other exported symbols in the same file.
208
+ *
209
+ * This handles cases like:
210
+ * - ChatResponse used as return type in sendMessage()
211
+ * - ActiveDeliverable used in ActiveProject interface
212
+ * - PYTHON_BUILTINS used in isStandardLibrary() via PYTHON_BUILTINS.has()
213
+ * - Methods within exported service objects (e.g., supportChatService.startNewConversation)
214
+ *
215
+ * If a type/constant is used by an exported function/class, it should not be flagged as unused.
216
+ *
217
+ * @param symbolName - Name of the symbol to check
218
+ * @param definedInFile - File path where the symbol is defined
219
+ * @param context - Project context with file information
220
+ * @param symbolsImportedFromFile - Map of file paths to sets of imported symbol names
221
+ * @returns true if the symbol is used in same-file exports, false otherwise
222
+ */
223
+ export async function isSymbolUsedInSameFileExports(symbolName, definedInFile, context, _symbolsImportedFromFile) {
224
+ const fileInfo = context.files.get(definedInFile);
225
+ if (!fileInfo)
226
+ return false;
227
+ // Check if this symbol is a method/property of an exported parent object
228
+ // e.g., startNewConversation is a method of supportChatService which is exported
229
+ const symbol = fileInfo.symbols.find(s => s.name === symbolName);
230
+ if (symbol?.scope) {
231
+ // This symbol has a scope (parent object) - check if the parent is exported
232
+ const parentSymbol = fileInfo.symbols.find(s => s.name === symbol.scope);
233
+ if (parentSymbol?.exported) {
234
+ // The parent object is exported, so this method is accessible via the parent
235
+ return true;
236
+ }
237
+ }
238
+ // OPTIMIZED: Check if symbol is used by other exports in the same file
239
+ // by examining the import/export relationships in the context
240
+ // Check if any other symbol in the same file references this symbol
241
+ for (const sym of fileInfo.symbols) {
242
+ if (sym.name === symbolName)
243
+ continue;
244
+ // If another exported symbol exists, check if this symbol is used in its context
245
+ if (sym.exported && sym.returnType?.includes(symbolName)) {
246
+ return true;
247
+ }
248
+ }
249
+ // Check for member access patterns using cached file content
250
+ try {
251
+ let content = fileContentCache.get(definedInFile);
252
+ if (!content) {
253
+ content = await fs.readFile(definedInFile, "utf-8");
254
+ fileContentCache.set(definedInFile, content);
255
+ }
256
+ // Pattern 1: symbolName followed by a dot (member access via dot notation)
257
+ // Pattern 2: symbolName followed by [ (member access via bracket notation)
258
+ const memberAccessPattern = new RegExp(`\\b${escapeRegex(symbolName)}\\s*[.\\[]`, "g");
259
+ // Count occurrences, excluding the definition line itself
260
+ const matches = content.match(memberAccessPattern);
261
+ if (matches && matches.length > 0) {
262
+ // Found member access - the symbol is being used
263
+ return true;
264
+ }
265
+ }
266
+ catch {
267
+ // File read failed, continue with other checks
268
+ }
269
+ return false;
270
+ }
271
+ /**
272
+ * Escape special regex characters in a string
273
+ */
274
+ function escapeRegex(str) {
275
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
276
+ }
277
+ /**
278
+ * Check if a symbol is used anywhere in the codebase using AST analysis.
279
+ *
280
+ * This is purely AST-based - no regex patterns. It checks for:
281
+ * - Direct imports of the symbol
282
+ * - Type annotations (: TypeName) via AST
283
+ * - Generic parameters (<TypeName>) via AST
284
+ * - Return types via AST
285
+ * - Extends/implements clauses via AST
286
+ * - Property types in interfaces via AST
287
+ * - Function/method call usage via AST
288
+ *
289
+ * Uses caching to avoid redundant checks.
290
+ *
291
+ * @param symbolName - Name of the symbol to check
292
+ * @param definedInFile - File path where the symbol is defined
293
+ * @param context - Project context with file information
294
+ * @param symbolsImportedFromFile - Map of file paths to sets of imported symbol names
295
+ * @returns true if the symbol is used anywhere, false otherwise
296
+ */
297
+ async function isSymbolUsedAnywhere(symbolName, definedInFile, context, symbolsImportedFromFile) {
298
+ // Check cache first
299
+ const cacheKey = `${definedInFile}:${symbolName}`;
300
+ const cached = symbolUsageCache.get(cacheKey);
301
+ if (cached !== undefined) {
302
+ return cached;
303
+ }
304
+ // Check 1: Is it directly imported anywhere? (fastest check)
305
+ if (isSymbolImported(symbolName, definedInFile, symbolsImportedFromFile)) {
306
+ symbolUsageCache.set(cacheKey, true);
307
+ return true;
308
+ }
309
+ // Check 2: Is it referenced as a type anywhere?
310
+ if (await isSymbolTypeReferenced(symbolName, definedInFile, context)) {
311
+ symbolUsageCache.set(cacheKey, true);
312
+ return true;
313
+ }
314
+ // Check 3: Is it called or instantiated anywhere?
315
+ // Look up the symbol to get its scope (for method detection)
316
+ const fileInfo = context.files.get(definedInFile);
317
+ const symbol = fileInfo?.symbols.find(s => s.name === symbolName);
318
+ if (await isSymbolCalledOrInstantiated(symbolName, context, symbol?.scope)) {
319
+ symbolUsageCache.set(cacheKey, true);
320
+ return true;
321
+ }
322
+ // Check 4: Is it used within other exported symbols in the same file?
323
+ if (await isSymbolUsedInSameFileExports(symbolName, definedInFile, context, symbolsImportedFromFile)) {
324
+ symbolUsageCache.set(cacheKey, true);
325
+ return true;
326
+ }
327
+ symbolUsageCache.set(cacheKey, false);
328
+ return false;
329
+ }
330
+ // ============================================================================
331
+ // Main Dead Code Detection Function
332
+ // ============================================================================
333
+ /**
334
+ * Detect unused exports, orphaned files, and unused functions in a codebase.
335
+ *
336
+ * Uses a multi-phase analysis approach:
337
+ * 1. Build import graph and JSX component usage map
338
+ * 2. Preload type references for files with exports (for performance)
339
+ * 3. Phase 1: Quick filter using import graph (O(1) lookup)
340
+ * 4. Phase 2: Deep AST analysis for potentially unused symbols (limited scope)
341
+ * 5. Find orphaned files (files with exports but no importers)
342
+ * 6. Check for unused functions in new code (if provided)
343
+ *
344
+ * Performance optimizations:
345
+ * - Limits number of exports to check (MAX_EXPORTS_TO_CHECK = 300)
346
+ * - Limits deep analysis scope (MAX_DEEP_ANALYSIS = 150)
347
+ * - Uses batch processing for file preloading and analysis
348
+ * - Maintains caches for file contents, type references, and symbol usage
349
+ * - Returns at most 30 issues to avoid overwhelming output
350
+ *
351
+ * @param context - Project context with file and dependency information
352
+ * @param newCode - Optional new code to check for unused functions
353
+ * @returns Array of dead code issues found
354
+ */
355
+ export async function detectDeadCode(context, newCode, filePathFilter, perFileOnly) {
356
+ const issues = [];
357
+ // Per-file mode: only check for unused functions/constants in newCode.
358
+ // Skip ALL project-wide scans (unused exports, orphaned files) — these are slow,
359
+ // race-prone during rapid vibecoding, and belong in the initial health check.
360
+ if (perFileOnly && newCode) {
361
+ return detectUnusedLocals(newCode, "(new code)");
362
+ }
363
+ else if (perFileOnly) {
364
+ return issues; // No newCode to check
365
+ }
366
+ // Load ignore patterns
367
+ const ignorePatterns = await loadIgnorePatterns(context.projectPath);
368
+ // Clear only the working caches, preserve mtime cache for incremental analysis
369
+ fileContentCache.clear();
370
+ typeReferencesCache.clear();
371
+ symbolUsageCache.clear();
372
+ // fileMtimeCache is preserved to enable incremental analysis
373
+ // Build map of which symbols are imported FROM each file
374
+ const symbolsImportedFromFile = new Map();
375
+ const jsxUsedComponents = new Set();
376
+ // Track all imports across the project
377
+ for (const dep of context.dependencies) {
378
+ if (!symbolsImportedFromFile.has(dep.to)) {
379
+ symbolsImportedFromFile.set(dep.to, new Set());
380
+ }
381
+ for (const sym of dep.importedSymbols) {
382
+ symbolsImportedFromFile.get(dep.to).add(sym);
383
+ }
384
+ }
385
+ // Track JSX and Wildcard usage + build raw import name set for safety checks
386
+ const wildcardImportedModules = new Map(); // Local Name -> Module Path
387
+ const allFileKeysEarly = Array.from(context.files.keys()); // Cache once for all resolveImport calls
388
+ // allRawImportedNames: ALL symbol names imported anywhere, from raw file analysis.
389
+ // Unlike symbolsImportedFromFile (which depends on dependency resolution), this set
390
+ // is always up-to-date because it comes from the parsed import statements directly.
391
+ // Used as a safety net in orphaned file detection to catch graph staleness.
392
+ const allRawImportedNames = new Set();
393
+ for (const [filePath, fileInfo] of context.files) {
394
+ for (const imp of fileInfo.imports) {
395
+ if (imp.defaultImport && /^[A-Z]/.test(imp.defaultImport)) {
396
+ jsxUsedComponents.add(imp.defaultImport);
397
+ }
398
+ for (const name of imp.namedImports) {
399
+ if (/^[A-Z]/.test(name)) {
400
+ jsxUsedComponents.add(name);
401
+ }
402
+ }
403
+ // Build raw import name set (all named + default imports regardless of resolution)
404
+ if (imp.defaultImport) {
405
+ allRawImportedNames.add(imp.defaultImport);
406
+ }
407
+ for (const name of imp.namedImports) {
408
+ allRawImportedNames.add(name);
409
+ }
410
+ // Track Wildcard imports: import * as utils from './utils'
411
+ if (imp.namespaceImport) {
412
+ const resolved = resolveImport(imp.source, filePath, allFileKeysEarly);
413
+ if (resolved) {
414
+ wildcardImportedModules.set(imp.namespaceImport, resolved);
415
+ }
416
+ }
417
+ }
418
+ }
419
+ // OPTIMIZED: Use cached context data instead of re-parsing files
420
+ // The context already has imports, exports, and symbols from project analysis
421
+ logger.debug("Using cached context data for dead code detection...");
422
+ // Build string literal usage from cached keywords in context
423
+ // This is much faster than re-reading and parsing all files
424
+ const stringLiteralUsage = new Set();
425
+ for (const [filePath, fileInfo] of context.files) {
426
+ // Use keywords from context (already extracted during project build)
427
+ for (const keyword of fileInfo.keywords) {
428
+ if (keyword.length >= 3) {
429
+ stringLiteralUsage.add(keyword);
430
+ }
431
+ }
432
+ }
433
+ // OPTIMIZED: Use cached imports from context for wildcard usage tracking
434
+ // Pre-build a reverse index: import_source → Set<namedImport> (single O(n) pass)
435
+ // This replaces the previous O(n²) nested loop over all files
436
+ const sourceToNamedImports = new Map();
437
+ for (const [, fileInfo] of context.files) {
438
+ for (const imp of fileInfo.imports) {
439
+ if (imp.namedImports.length > 0) {
440
+ if (!sourceToNamedImports.has(imp.source)) {
441
+ sourceToNamedImports.set(imp.source, new Set());
442
+ }
443
+ const set = sourceToNamedImports.get(imp.source);
444
+ for (const sym of imp.namedImports) {
445
+ set.add(sym);
446
+ }
447
+ }
448
+ }
449
+ }
450
+ // Now resolve namespace imports using the pre-built index (O(n) pass)
451
+ const allFileKeys = Array.from(context.files.keys()); // Cache to avoid repeated Array.from
452
+ for (const [filePath, fileInfo] of context.files) {
453
+ for (const imp of fileInfo.imports) {
454
+ if (imp.namespaceImport) {
455
+ const resolved = resolveImport(imp.source, filePath, allFileKeys);
456
+ if (resolved) {
457
+ const namedImports = sourceToNamedImports.get(imp.source);
458
+ if (namedImports && namedImports.size > 0) {
459
+ if (!symbolsImportedFromFile.has(resolved)) {
460
+ symbolsImportedFromFile.set(resolved, new Set());
461
+ }
462
+ const importSet = symbolsImportedFromFile.get(resolved);
463
+ for (const sym of namedImports) {
464
+ importSet.add(sym);
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
470
+ }
471
+ // Identify files that need deep analysis (have exports but not in import graph)
472
+ const filesToPreload = new Set();
473
+ for (const [filePath, fileInfo] of context.files) {
474
+ if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
475
+ continue;
476
+ if (fileInfo.exports.length === 0 && !fileInfo.symbols.some((s) => s.exported))
477
+ continue;
478
+ // Only deep-analyze if exports aren't in import graph
479
+ const hasUnimportedExports = fileInfo.exports.some(exp => !isSymbolImported(exp.name, filePath, symbolsImportedFromFile));
480
+ if (hasUnimportedExports) {
481
+ filesToPreload.add(filePath);
482
+ }
483
+ }
484
+ logger.debug(`Using cached context: ${stringLiteralUsage.size} keywords, ${filesToPreload.size} files need deep analysis`);
485
+ // Collect all exported symbols to check
486
+ const exportsToCheck = [];
487
+ for (const [filePath, fileInfo] of context.files) {
488
+ if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
489
+ continue;
490
+ // Check against ignore patterns
491
+ if (isIgnored(fileInfo.relativePath, ignorePatterns))
492
+ continue;
493
+ // Apply scope filter (e.g., only backend or frontend files)
494
+ if (filePathFilter && !filePathFilter(filePath))
495
+ continue;
496
+ // Collect from exports list
497
+ for (const exp of fileInfo.exports) {
498
+ const symbol = fileInfo.symbols.find((s) => s.name === exp.name);
499
+ // SKIP type-only exports (interfaces, type aliases, enums)
500
+ // These are compile-time constructs and don't generate runtime code
501
+ // They should not be flagged as "dead code"
502
+ if (symbol?.kind === "interface" || symbol?.kind === "type") {
503
+ continue;
504
+ }
505
+ exportsToCheck.push({
506
+ name: exp.name,
507
+ file: filePath,
508
+ relativePath: fileInfo.relativePath,
509
+ kind: symbol?.kind,
510
+ scope: symbol?.scope,
511
+ });
512
+ }
513
+ // Collect from symbols with export flag
514
+ for (const sym of fileInfo.symbols) {
515
+ if (!sym.exported)
516
+ continue;
517
+ if (exportsToCheck.some((e) => e.name === sym.name && e.file === filePath))
518
+ continue;
519
+ // SKIP type-only symbols (interfaces, type aliases, enums)
520
+ // These are compile-time constructs and don't generate runtime code
521
+ if (sym.kind === "interface" || sym.kind === "type") {
522
+ continue;
523
+ }
524
+ exportsToCheck.push({
525
+ name: sym.name,
526
+ file: filePath,
527
+ relativePath: fileInfo.relativePath,
528
+ kind: sym.kind,
529
+ scope: sym.scope,
530
+ });
531
+ }
532
+ }
533
+ // Limit the number of exports to check to prevent timeout
534
+ const MAX_EXPORTS_TO_CHECK = 300; // Increased for better coverage
535
+ let limitWarning = "";
536
+ if (exportsToCheck.length > MAX_EXPORTS_TO_CHECK) {
537
+ limitWarning = `Note: Limited analysis to ${MAX_EXPORTS_TO_CHECK} of ${exportsToCheck.length} exports for performance. Run on smaller scopes for complete coverage.`;
538
+ logger.warn(`Too many exports (${exportsToCheck.length}), limiting to ${MAX_EXPORTS_TO_CHECK} for performance`);
539
+ // Prioritize checking exports from non-test, non-config files
540
+ exportsToCheck.sort((a, b) => {
541
+ const aIsTest = a.file.includes("test") || a.file.includes("spec");
542
+ const bIsTest = b.file.includes("test") || b.file.includes("spec");
543
+ if (aIsTest && !bIsTest)
544
+ return 1;
545
+ if (!aIsTest && bIsTest)
546
+ return -1;
547
+ return 0;
548
+ });
549
+ exportsToCheck.splice(MAX_EXPORTS_TO_CHECK);
550
+ }
551
+ // Check exports with a hybrid approach: fast import check + selective deep analysis
552
+ logger.debug(`Checking ${exportsToCheck.length} exports using hybrid analysis...`);
553
+ // Phase 1: Quick filter - check import graph (fast, O(1) lookup)
554
+ const potentiallyUnused = [];
555
+ for (const exp of exportsToCheck) {
556
+ const isPascalCase = /^[A-Z][a-zA-Z0-9]*$/.test(exp.name);
557
+ const isUsedAsJSX = isPascalCase && jsxUsedComponents.has(exp.name);
558
+ // Skip JSX components that are used
559
+ if (isUsedAsJSX)
560
+ continue;
561
+ // Fast check: Is it directly imported or reflectively referenced?
562
+ const isImported = isSymbolImported(exp.name, exp.file, symbolsImportedFromFile);
563
+ // Reflective usage check: Is this symbol name used in any string literal?
564
+ // This perfectly handles dynamic registration (e.g. tools, handlers, routes)
565
+ const isReflective = stringLiteralUsage.has(exp.name);
566
+ if (!isImported && !isReflective) {
567
+ // Potentially unused - needs deeper analysis
568
+ potentiallyUnused.push(exp);
569
+ }
570
+ }
571
+ logger.debug(`Phase 1: ${potentiallyUnused.length}/${exportsToCheck.length} exports not imported, running deep analysis...`);
572
+ // Log if we're checking a limited set
573
+ if (exportsToCheck.length < context.files.size * 5) {
574
+ // Rough heuristic: if we're checking fewer exports than 5x files, we likely hit a limit
575
+ logger.info(`Analyzing ${exportsToCheck.length} exports (may be limited for performance)`);
576
+ }
577
+ // Phase 2: Deep analysis for potentially unused exports (in smaller batches with limits)
578
+ const MAX_DEEP_ANALYSIS = 150; // Increased for better coverage
579
+ const toAnalyze = potentiallyUnused.slice(0, MAX_DEEP_ANALYSIS);
580
+ if (potentiallyUnused.length > MAX_DEEP_ANALYSIS) {
581
+ const deepLimitWarning = `Limited deep analysis to ${MAX_DEEP_ANALYSIS} of ${potentiallyUnused.length} potentially unused exports.`;
582
+ limitWarning =
583
+ limitWarning ? `${limitWarning} ${deepLimitWarning}` : deepLimitWarning;
584
+ logger.warn(deepLimitWarning);
585
+ }
586
+ // Phase 2: Deep analysis - process potentially unused exports in smaller batches
587
+ // This prevents overwhelming the event loop and allows for periodic yielding
588
+ const DEEP_BATCH_SIZE = 20;
589
+ for (let i = 0; i < toAnalyze.length; i += DEEP_BATCH_SIZE) {
590
+ const batch = toAnalyze.slice(i, i + DEEP_BATCH_SIZE);
591
+ const batchResults = await Promise.all(batch.map(async (exp) => {
592
+ // Check cache first to avoid redundant work
593
+ const cacheKey = `${exp.file}:${exp.name}`;
594
+ const cached = symbolUsageCache.get(cacheKey);
595
+ if (cached !== undefined) {
596
+ return cached ? null : ({
597
+ type: "unusedExport",
598
+ severity: "low", // Downgrade from medium to low
599
+ name: exp.name,
600
+ file: exp.relativePath,
601
+ message: `Export '${exp.name}' is never used anywhere in the codebase`,
602
+ });
603
+ }
604
+ // Check same-file usage first (fastest - uses cached file content)
605
+ const isUsedInSameFile = await isSymbolUsedInSameFileExports(exp.name, exp.file, context, symbolsImportedFromFile);
606
+ if (isUsedInSameFile) {
607
+ symbolUsageCache.set(cacheKey, true);
608
+ return null;
609
+ }
610
+ // Then check type references (uses preloaded cache)
611
+ const isTypeReferenced = await isSymbolTypeReferenced(exp.name, exp.file, context);
612
+ if (isTypeReferenced) {
613
+ symbolUsageCache.set(cacheKey, true);
614
+ return null;
615
+ }
616
+ // Finally check function calls (most expensive)
617
+ const isCalled = await isSymbolCalledOrInstantiated(exp.name, context, exp.scope);
618
+ if (isCalled) {
619
+ symbolUsageCache.set(cacheKey, true);
620
+ return null;
621
+ }
622
+ // Truly unused
623
+ symbolUsageCache.set(cacheKey, false);
624
+ return {
625
+ type: "unusedExport",
626
+ severity: "low", // Downgrade from medium to low
627
+ name: exp.name,
628
+ file: exp.relativePath,
629
+ message: `Export '${exp.name}' is never used anywhere in the codebase`,
630
+ };
631
+ }));
632
+ // Collect non-null results (truly unused exports)
633
+ for (const result of batchResults) {
634
+ if (result &&
635
+ !issues.some((i) => i.name === result.name && i.file === result.file)) {
636
+ issues.push(result);
637
+ }
638
+ }
639
+ // Yield to event loop after each batch
640
+ await new Promise((resolve) => setImmediate(resolve));
641
+ }
642
+ // Find orphaned files (files with exports but never imported AND no symbols used)
643
+ for (const [filePath, fileInfo] of context.files) {
644
+ if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
645
+ continue;
646
+ const importers = context.reverseImportGraph.get(filePath) || [];
647
+ const hasExports = fileInfo.exports.length > 0 || fileInfo.symbols.some((s) => s.exported);
648
+ if (importers.length === 0 && hasExports) {
649
+ // Python-specific: Check if this file is re-exported via __init__.py
650
+ // In Python, files exported through __init__.py are NOT orphaned
651
+ if (context.language === "python") {
652
+ const dir = path.dirname(filePath);
653
+ const initPy = path.join(dir, "__init__.py");
654
+ const initInfo = context.files.get(initPy);
655
+ if (initInfo) {
656
+ // Check if __init__.py imports from this module
657
+ const moduleName = path.basename(filePath, ".py");
658
+ const isReExported = initInfo.imports.some((imp) => imp.source === `.${moduleName}` ||
659
+ imp.source === `./${moduleName}` ||
660
+ imp.source === moduleName ||
661
+ imp.source.endsWith(`.${moduleName}`) ||
662
+ imp.namedImports.some((n) => fileInfo.symbols.some((s) => s.name === n && s.exported)));
663
+ if (isReExported)
664
+ continue; // Not orphaned — re-exported via __init__.py
665
+ // Also check if __init__.py content references this module via text scan
666
+ // Handles: from .module import *, import patterns in __init__.py
667
+ const initContent = fileContentCache.get(initPy);
668
+ if (initContent) {
669
+ const regex = new RegExp(`\\b${moduleName}\\b`);
670
+ if (regex.test(initContent))
671
+ continue;
672
+ }
673
+ else {
674
+ // Try to read __init__.py to check
675
+ try {
676
+ const content = await fs.readFile(initPy, "utf-8");
677
+ fileContentCache.set(initPy, content);
678
+ const regex = new RegExp(`\\b${moduleName}\\b`);
679
+ if (regex.test(content))
680
+ continue;
681
+ }
682
+ catch {
683
+ // Can't read, proceed with orphan check
684
+ }
685
+ }
686
+ }
687
+ }
688
+ // Python-specific: Check if any file imports this module by name from the parent package.
689
+ // e.g., `from app.services import planning_service` resolves to __init__.py in the
690
+ // dependency graph, but actually means planning_service.py is being used.
691
+ if (context.language === "python") {
692
+ const moduleName = path.basename(filePath, ".py");
693
+ let isModuleImported = false;
694
+ for (const [, otherFileInfo] of context.files) {
695
+ for (const imp of otherFileInfo.imports) {
696
+ if (imp.namedImports.includes(moduleName)) {
697
+ isModuleImported = true;
698
+ break;
699
+ }
700
+ }
701
+ if (isModuleImported)
702
+ break;
703
+ }
704
+ if (isModuleImported)
705
+ continue;
706
+ }
707
+ // Double-check: are ANY of this file's exports used in OTHER files?
708
+ // Important: only check cross-file usage (imports, type refs, calls from other files).
709
+ // Do NOT use isSymbolUsedAnywhere here — its same-file scope check
710
+ // (isSymbolUsedInSameFileExports) falsely marks files as "used" when methods
711
+ // have an exported parent object, even though no other file imports from this file.
712
+ let anyExportUsed = false;
713
+ for (const exp of fileInfo.exports) {
714
+ // Check: Is it directly imported by another file?
715
+ // This is the ONLY reliable check for orphaned files because it scopes
716
+ // by defining file. isSymbolTypeReferenced and isSymbolCalledOrInstantiated
717
+ // match by NAME only, producing false positives from name collisions
718
+ // (e.g., "buildQueryString" used in another file from a different definition).
719
+ // Since importers.length === 0 (reverseImportGraph), we already know no file
720
+ // imports from this file, but symbolsImportedFromFile (from dependencies)
721
+ // provides a second opinion.
722
+ if (isSymbolImported(exp.name, filePath, symbolsImportedFromFile)) {
723
+ anyExportUsed = true;
724
+ break;
725
+ }
726
+ }
727
+ // Safety net: check against RAW import data (immune to dependency graph staleness).
728
+ // During rapid vibecoding, the dependency graph can be temporarily stale
729
+ // (e.g., reverseImportGraph missing entries because refreshFileContext hasn't
730
+ // re-resolved all importers). Raw import names come directly from file analysis
731
+ // and are always up-to-date.
732
+ if (!anyExportUsed) {
733
+ for (const exp of fileInfo.exports) {
734
+ if (jsxUsedComponents.has(exp.name) || allRawImportedNames.has(exp.name)) {
735
+ anyExportUsed = true;
736
+ break;
737
+ }
738
+ }
739
+ // Also check symbols (some exports are tracked as symbols, not in exports array)
740
+ if (!anyExportUsed) {
741
+ for (const sym of fileInfo.symbols) {
742
+ if (sym.exported && (jsxUsedComponents.has(sym.name) || allRawImportedNames.has(sym.name))) {
743
+ anyExportUsed = true;
744
+ break;
745
+ }
746
+ }
747
+ }
748
+ }
749
+ if (!anyExportUsed) {
750
+ const alreadyFlagged = issues.some((i) => i.type === "orphanedFile" && i.file === fileInfo.relativePath);
751
+ if (!alreadyFlagged) {
752
+ issues.push({
753
+ type: "orphanedFile",
754
+ severity: "low",
755
+ name: fileInfo.relativePath,
756
+ file: fileInfo.relativePath,
757
+ message: `File has exports but nothing is used anywhere`,
758
+ });
759
+ }
760
+ }
761
+ }
762
+ }
763
+ // Check for unused functions in new code
764
+ if (newCode) {
765
+ const definedSymbols = new Set();
766
+ const usedSymbols = new Set();
767
+ const defPatterns = [
768
+ { pattern: /function\s+([a-zA-Z0-9_$]+)\s*\(/g, type: "function" },
769
+ { pattern: /(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\(/g, type: "function" },
770
+ { pattern: /(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*.*=>/g, type: "function" },
771
+ { pattern: /(?:const|let|var)\s+([A-Z0-9_$]{3,})\s*=/g, type: "constant" },
772
+ { pattern: /def\s+([a-zA-Z0-9_$]+)\s*\(/g, type: "function" },
773
+ { pattern: /([A-Z0-9_$]{3,})\s*=\s*/g, type: "constant" }, // Python constants
774
+ ];
775
+ for (const { pattern, type } of defPatterns) {
776
+ let match;
777
+ while ((match = pattern.exec(newCode)) !== null) {
778
+ definedSymbols.add({ name: match[1], type });
779
+ }
780
+ }
781
+ const usagePattern = /\b([a-zA-Z0-9_$]+)\b/g;
782
+ let match;
783
+ while ((match = usagePattern.exec(newCode)) !== null) {
784
+ usedSymbols.add(match[1]);
785
+ }
786
+ for (const sym of definedSymbols) {
787
+ // For each defined symbol, we expect at least 2 occurrences of the name
788
+ // (one for definition, one for usage).
789
+ const count = (newCode.match(new RegExp(`\\b${sym.name}\\b`, "g")) || []).length;
790
+ if (count <= 1) {
791
+ issues.push({
792
+ type: sym.type === "function" ? "unusedFunction" : "unusedExport",
793
+ severity: "medium",
794
+ name: sym.name,
795
+ file: "(new code)",
796
+ message: `${sym.type === "function" ? "Function" : "Constant"} '${sym.name}' is defined but never used in this code`,
797
+ });
798
+ }
799
+ }
800
+ }
801
+ // Clear cache after scan
802
+ fileContentCache.clear();
803
+ // Add informational message if we hit limits
804
+ if (limitWarning) {
805
+ logger.info(`Dead code analysis: ${limitWarning}`);
806
+ }
807
+ // Separate orphanedFile issues from other dead code issues.
808
+ // Limit non-orphan issues (unusedExport, unusedFunction) to 30 for performance,
809
+ // but always include ALL orphanedFile entries — they are high-signal and cheap.
810
+ const orphanIssues = issues.filter(i => i.type === "orphanedFile");
811
+ const otherIssues = issues.filter(i => i.type !== "orphanedFile");
812
+ return [...otherIssues.slice(0, 30), ...orphanIssues];
813
+ }
814
+ /**
815
+ * Clear all dead code detection caches
816
+ * Should be called between test runs to prevent interference
817
+ * Note: fileMtimeCache is preserved to enable incremental analysis
818
+ */
819
+ export function clearDeadCodeCaches() {
820
+ fileContentCache.clear();
821
+ typeReferencesCache.clear();
822
+ symbolUsageCache.clear();
823
+ // fileMtimeCache is intentionally NOT cleared to enable incremental analysis
824
+ }
825
+ /**
826
+ * Force clear ALL caches including mtime cache
827
+ * Use this when you want to force a full re-scan
828
+ */
829
+ export function forceFullScan() {
830
+ fileContentCache.clear();
831
+ typeReferencesCache.clear();
832
+ symbolUsageCache.clear();
833
+ fileMtimeCache.clear();
834
+ }
835
+ /**
836
+ * Load ignore patterns from .vibeguardignore or .gitignore
837
+ */
838
+ async function loadIgnorePatterns(projectPath) {
839
+ const patterns = [
840
+ "**/node_modules/**",
841
+ "**/dist/**",
842
+ "**/build/**",
843
+ "**/.git/**",
844
+ ];
845
+ try {
846
+ const ignoreFile = path.join(projectPath, ".vibeguardignore");
847
+ const content = await fs.readFile(ignoreFile, "utf-8");
848
+ patterns.push(...content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")));
849
+ }
850
+ catch {
851
+ // Fallback to basic patterns if file doesn't exist
852
+ }
853
+ return patterns;
854
+ }
855
+ /**
856
+ * Check if a file is ignored
857
+ */
858
+ function isIgnored(relativePath, patterns) {
859
+ return patterns.some((pattern) => minimatch(relativePath, pattern, { dot: true }));
860
+ }
861
+ //# sourceMappingURL=deadCode.js.map