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,1169 @@
1
+ /**
2
+ * Automated Finding Verifier - Batched & Concurrent
3
+ *
4
+ * This module automatically verifies validation findings to eliminate false positives
5
+ * without requiring human intervention. Uses batching and concurrency for performance.
6
+ *
7
+ * Verification Strategies:
8
+ * 1. Usage Pattern Analysis - Check for dynamic/indirect usage
9
+ * 2. Git History Analysis - Check if code is actually used in recent commits
10
+ * 3. Framework Pattern Detection - Recognize framework-specific patterns (callbacks, handlers)
11
+ * 4. Cross-File Reference Check - Look for references in other files
12
+ * 5. Test Coverage Analysis - Check if "dead" code is actually tested
13
+ *
14
+ * Performance Optimizations:
15
+ * - File-based batching: Groups findings by file to minimize I/O
16
+ * - Concurrent processing: Verifies multiple files in parallel with limits
17
+ * - Intelligent caching: File contents and git status cached per batch
18
+ * - Batched git operations: Single git status call for all files
19
+ *
20
+ * @format
21
+ */
22
+ import { logger } from "../utils/logger.js";
23
+ import { exec } from "child_process";
24
+ import { promisify } from "util";
25
+ const execAsync = promisify(exec);
26
+ import * as fs from "fs/promises";
27
+ import * as path from "path";
28
+ import { extractSymbolsAST } from "../tools/validation/extractors/index.js";
29
+ import { checkPackageRegistry } from "../tools/validation/registry.js";
30
+ import { impactAnalyzer } from "./impactAnalyzer.js";
31
+ /**
32
+ * Lightweight wrapper around the same semantic tracing that powers
33
+ * [`get_dependency_graph`](src/tools/getDependencyGraph.ts:61).
34
+ *
35
+ * Used during automated verification to eliminate "unused export" false positives
36
+ * without requiring the LLM to call a separate tool.
37
+ */
38
+ function getDependencyEvidenceForSymbol(symbolName, ctx, depth = 2, definedInFile) {
39
+ const graph = ctx.projectContext.symbolGraph;
40
+ if (!graph)
41
+ return null;
42
+ // Safety: only use dependency evidence when the symbol is actually defined in the
43
+ // file associated with the finding. This reduces false negatives from name collisions
44
+ // (e.g., multiple "getById" methods across different files).
45
+ if (definedInFile) {
46
+ const definingFiles = graph.symbolToFiles.get(symbolName);
47
+ if (!definingFiles || !definingFiles.has(definedInFile)) {
48
+ return null;
49
+ }
50
+ }
51
+ const blast = impactAnalyzer.traceBlastRadius(symbolName, graph, Math.min(depth, 5));
52
+ const isUsed = blast.affectedFiles.length > 0 || blast.impactedSymbols.length > 0;
53
+ return { blast, isUsed };
54
+ }
55
+ /**
56
+ * Detect file language from extension. Used when ctx.language is "all"
57
+ * in full-stack projects to ensure the correct parser is used per file.
58
+ */
59
+ function detectFileLanguage(filePath, fallback) {
60
+ const ext = path.extname(filePath).toLowerCase();
61
+ switch (ext) {
62
+ case ".py": return "python";
63
+ case ".ts":
64
+ case ".tsx": return "typescript";
65
+ case ".js":
66
+ case ".jsx":
67
+ case ".mjs":
68
+ case ".cjs": return "javascript";
69
+ default: return fallback === "all" ? "typescript" : fallback;
70
+ }
71
+ }
72
+ // ============================================================================
73
+ // Configuration
74
+ // ============================================================================
75
+ /** Maximum concurrent file verifications */
76
+ const MAX_CONCURRENCY = 8;
77
+ /** Batch size for findings within a file */
78
+ const FILE_BATCH_SIZE = 50;
79
+ // ============================================================================
80
+ // Main Entry Point - Batched & Concurrent
81
+ // ============================================================================
82
+ /**
83
+ * Automatically verify all findings using batched concurrent processing.
84
+ * Groups findings by file, caches file data, and processes files in parallel.
85
+ */
86
+ export async function verifyFindingsAutomatically(hallucinations, deadCode, projectContext, projectPath, language, onProgress) {
87
+ const allFindings = [...hallucinations, ...deadCode];
88
+ logger.info(`Starting batched verification of ${allFindings.length} findings ` +
89
+ `(${hallucinations.length} hallucinations, ${deadCode.length} dead code)...`);
90
+ const ctx = {
91
+ projectPath,
92
+ projectContext,
93
+ language,
94
+ gitAvailable: await checkGitAvailable(projectPath),
95
+ fileContentCache: new Map(),
96
+ };
97
+ // Group findings by file for efficient batch processing
98
+ const fileBatches = groupFindingsByFile(allFindings);
99
+ logger.info(`Grouped into ${fileBatches.length} file batches for concurrent processing`);
100
+ // Pre-batch git status for ALL files in a single git call (instead of per-file)
101
+ if (ctx.gitAvailable) {
102
+ ctx.gitStatusBatch = await batchGitStatus(ctx.projectPath);
103
+ ctx.featureBranchCached = await checkFeatureBranch(ctx);
104
+ }
105
+ const progress = {
106
+ totalFiles: fileBatches.length,
107
+ processedFiles: 0,
108
+ totalFindings: allFindings.length,
109
+ processedFindings: 0,
110
+ };
111
+ // Process file batches concurrently with limit
112
+ const verifiedResults = [];
113
+ for (let i = 0; i < fileBatches.length; i += MAX_CONCURRENCY) {
114
+ const batch = fileBatches.slice(i, i + MAX_CONCURRENCY);
115
+ // Process this concurrent batch
116
+ const batchResults = await Promise.all(batch.map(async (fileBatch) => {
117
+ // Create file cache for this file
118
+ const fileCache = {};
119
+ // Pre-load file data (git status, content if needed)
120
+ await preloadFileCache(fileBatch.filePath, ctx, fileCache);
121
+ // Verify all findings for this file
122
+ const results = [];
123
+ for (const finding of fileBatch.findings) {
124
+ const verified = await verifyFindingWithCache(finding, ctx, fileCache);
125
+ results.push(verified);
126
+ }
127
+ // Update progress
128
+ progress.processedFiles++;
129
+ progress.processedFindings += fileBatch.findings.length;
130
+ onProgress?.(progress);
131
+ return results;
132
+ }));
133
+ // Flatten results
134
+ for (const results of batchResults) {
135
+ verifiedResults.push(...results);
136
+ }
137
+ // Yield to event loop between batches
138
+ await new Promise((resolve) => setImmediate(resolve));
139
+ }
140
+ // Categorize results
141
+ const confirmed = verifiedResults.filter((v) => v.status === "confirmed");
142
+ const falsePositives = verifiedResults.filter((v) => v.status === "false_positive");
143
+ const uncertain = verifiedResults.filter((v) => v.status === "uncertain");
144
+ logger.info(`Verification complete: ${confirmed.length} confirmed, ` +
145
+ `${falsePositives.length} false positives, ${uncertain.length} uncertain ` +
146
+ `(${fileBatches.length} files processed)`);
147
+ return {
148
+ confirmed,
149
+ falsePositives,
150
+ uncertain,
151
+ stats: {
152
+ totalAnalyzed: allFindings.length,
153
+ confirmedCount: confirmed.length,
154
+ falsePositiveCount: falsePositives.length,
155
+ uncertainCount: uncertain.length,
156
+ },
157
+ };
158
+ }
159
+ // ============================================================================
160
+ // Batching & Caching
161
+ // ============================================================================
162
+ function groupFindingsByFile(findings) {
163
+ const fileMap = new Map();
164
+ for (const finding of findings) {
165
+ // Use a virtual key for findings without a file path (inline newCode validation)
166
+ const filePath = finding.file || "(inline)";
167
+ if (!fileMap.has(filePath)) {
168
+ fileMap.set(filePath, []);
169
+ }
170
+ fileMap.get(filePath).push(finding);
171
+ }
172
+ return Array.from(fileMap.entries()).map(([filePath, findings]) => ({
173
+ filePath,
174
+ findings,
175
+ }));
176
+ }
177
+ async function preloadFileCache(filePath, ctx, cache) {
178
+ // Use pre-batched git status (single git call for all files)
179
+ if (ctx.gitAvailable && ctx.gitStatusBatch) {
180
+ cache.gitStatus = ctx.gitStatusBatch.get(filePath) ?? { isNew: false, isModified: false };
181
+ }
182
+ // Feature branch is already cached on context during init
183
+ if (ctx.gitAvailable) {
184
+ cache.featureBranch = ctx.featureBranchCached;
185
+ }
186
+ }
187
+ async function verifyFindingWithCache(finding, ctx, cache) {
188
+ // Route to appropriate verifier based on finding type
189
+ if ("type" in finding && isValidationIssue(finding)) {
190
+ return await verifyHallucinationWithCache(finding, ctx, cache);
191
+ }
192
+ else {
193
+ return await verifyDeadCodeWithCache(finding, ctx, cache);
194
+ }
195
+ }
196
+ function isValidationIssue(finding) {
197
+ return "code" in finding && typeof finding.line === "number";
198
+ }
199
+ // ============================================================================
200
+ // Hallucination Verification with Caching
201
+ // ============================================================================
202
+ async function verifyHallucinationWithCache(issue, ctx, cache) {
203
+ const reasons = [];
204
+ let confidence = 0;
205
+ let status = "uncertain";
206
+ let method = "";
207
+ switch (issue.type) {
208
+ case "nonExistentFunction":
209
+ case "nonExistentClass":
210
+ case "undefinedVariable": {
211
+ // Extra guard: if the symbol is defined locally in the same file
212
+ // (e.g., function parameter, destructured param, local const/let), this is a false positive.
213
+ if (issue.type === "undefinedVariable") {
214
+ const localDef = await checkSymbolDefinedLocally(issue, ctx, cache);
215
+ if (localDef.isDefined) {
216
+ status = "false_positive";
217
+ confidence = 95;
218
+ method = "local_scope";
219
+ reasons.push(`Symbol is defined locally in the file (${localDef.hint})`);
220
+ reasons.push("Not a hallucination - this is valid local scope");
221
+ break;
222
+ }
223
+ }
224
+ // Check if symbol exists in any form (maybe just not imported)
225
+ const existsInProject = checkSymbolExistsInProject(issue, ctx);
226
+ if (existsInProject.exists) {
227
+ status = "false_positive";
228
+ confidence = 90;
229
+ method = "cross_reference";
230
+ reasons.push(`Symbol exists in project at ${existsInProject.location}`);
231
+ reasons.push("This is a missing import, not a hallucination");
232
+ }
233
+ else {
234
+ // Use cached future feature detection
235
+ const futureFeatureCheck = await detectFutureFeatureWithCache(issue, ctx, cache);
236
+ if (futureFeatureCheck.isFutureFeature) {
237
+ status = "false_positive";
238
+ confidence = futureFeatureCheck.confidence;
239
+ method = "future_feature_detection";
240
+ reasons.push("This appears to be part of an incomplete/new feature:");
241
+ reasons.push(...futureFeatureCheck.reasons);
242
+ reasons.push("Not a hallucination - code is for planned functionality");
243
+ }
244
+ else {
245
+ // Quick git check using cached status
246
+ if (cache.gitStatus?.isNew || cache.gitStatus?.isModified) {
247
+ status = "false_positive";
248
+ confidence = 85;
249
+ method = "git_history";
250
+ reasons.push("Symbol found in recent git changes");
251
+ reasons.push("This may be part of an incomplete feature");
252
+ }
253
+ else {
254
+ status = "confirmed";
255
+ confidence = 95;
256
+ method = "static_analysis";
257
+ reasons.push("Symbol not found anywhere in project");
258
+ reasons.push("No indicators of planned/incomplete feature");
259
+ reasons.push("This is a true hallucination");
260
+ }
261
+ }
262
+ }
263
+ break;
264
+ }
265
+ case "nonExistentImport": {
266
+ const moduleCheck = await checkModuleExports(issue, ctx);
267
+ if (moduleCheck.moduleExists && !moduleCheck.exportExists) {
268
+ status = "confirmed";
269
+ confidence = 98;
270
+ method = "module_resolution";
271
+ reasons.push("Module exists but export does not");
272
+ reasons.push("This is a true hallucination");
273
+ }
274
+ else if (!moduleCheck.moduleExists) {
275
+ status = "confirmed";
276
+ confidence = 99;
277
+ method = "module_resolution";
278
+ reasons.push("Module does not exist in project");
279
+ reasons.push("This is a true hallucination");
280
+ }
281
+ else {
282
+ status = "false_positive";
283
+ confidence = 80;
284
+ method = "module_resolution";
285
+ reasons.push("Module and export both exist");
286
+ reasons.push("May be a resolution path issue");
287
+ }
288
+ break;
289
+ }
290
+ case "dependencyHallucination": {
291
+ const pkgName = extractPackageName(issue.message);
292
+ const fileLang = issue.file ? detectFileLanguage(issue.file, ctx.language) : ctx.language;
293
+ const existsOnNpm = await checkPackageExistsOnRegistry(pkgName, fileLang);
294
+ if (!existsOnNpm) {
295
+ status = "confirmed";
296
+ confidence = 99;
297
+ method = "registry_check";
298
+ reasons.push("Package does not exist on npm registry");
299
+ reasons.push("This is definitely a hallucination");
300
+ }
301
+ else {
302
+ status = "false_positive";
303
+ confidence = 85;
304
+ method = "registry_check";
305
+ reasons.push("Package exists on npm registry");
306
+ reasons.push("This is a missing dependency, not a hallucination");
307
+ }
308
+ break;
309
+ }
310
+ case "missingDependency": {
311
+ // Check if the package exists in a nearby package.json (subdirectory).
312
+ // The manifest loader may have already been fixed, but this is a safety net.
313
+ const missingPkgName = extractPackageName(issue.message);
314
+ if (missingPkgName) {
315
+ const foundInSubdir = await checkPackageInSubdirectoryManifest(missingPkgName, ctx.projectPath);
316
+ if (foundInSubdir) {
317
+ status = "false_positive";
318
+ confidence = 98;
319
+ method = "subdirectory_manifest_check";
320
+ reasons.push(`Package '${missingPkgName}' found in a subdirectory package.json`);
321
+ reasons.push("This is not missing — the manifest loader missed it");
322
+ break;
323
+ }
324
+ }
325
+ // If not found in subdirectory, it's a genuine missing dependency (but low severity)
326
+ status = "confirmed";
327
+ confidence = 70;
328
+ method = "manifest_check";
329
+ reasons.push("Package not found in any project manifest");
330
+ reasons.push("This is a missing dependency (not a hallucination — it exists on the registry)");
331
+ break;
332
+ }
333
+ case "wrongParamCount": {
334
+ const paramCheck = await verifyParameterCount(issue, ctx);
335
+ if (paramCheck.isMismatch) {
336
+ status = "confirmed";
337
+ confidence = 92;
338
+ method = "signature_analysis";
339
+ reasons.push("Parameter count mismatch confirmed");
340
+ reasons.push(`Expected ${paramCheck.expected}, got ${paramCheck.actual}`);
341
+ }
342
+ else {
343
+ status = "false_positive";
344
+ confidence = 88;
345
+ method = "signature_analysis";
346
+ reasons.push("Parameter count appears correct on re-check");
347
+ reasons.push("May be variadic or have optional parameters");
348
+ }
349
+ break;
350
+ }
351
+ case "unusedImport": {
352
+ // Special case: async validation injects per-file "unused local" findings into the
353
+ // ValidationIssue stream using type=unusedImport.
354
+ // These findings look like:
355
+ // "Variable 'getRecipeDetails' is defined but never used in this file"
356
+ // For exported/service methods, this is frequently a false positive.
357
+ // Use dependency tracing (same engine as get_dependency_graph) to confirm.
358
+ if (issue.message.includes("defined but never used in this file") &&
359
+ typeof issue.file === "string" &&
360
+ issue.file.length > 0) {
361
+ const symbolName = extractSymbolName(issue);
362
+ if (symbolName) {
363
+ const dep = getDependencyEvidenceForSymbol(symbolName, ctx, 2, issue.file);
364
+ if (dep?.isUsed) {
365
+ status = "false_positive";
366
+ confidence = 98;
367
+ method = "dependency_graph";
368
+ reasons.push(`Dependency graph shows '${symbolName}' is referenced by ${dep.blast.affectedFiles.length} file(s).`);
369
+ const sampleFiles = dep.blast.affectedFiles
370
+ .slice(0, 4)
371
+ .map((f) => f)
372
+ .join(", ");
373
+ if (sampleFiles) {
374
+ reasons.push(`Example consumers: ${sampleFiles}${dep.blast.affectedFiles.length > 4 ? ", …" : ""}`);
375
+ }
376
+ reasons.push("Not an unused local — this symbol is reachable via cross-file consumers");
377
+ break;
378
+ }
379
+ }
380
+ }
381
+ const usageCheck = await checkImportUsage(issue, ctx);
382
+ if (usageCheck.isUsed) {
383
+ status = "false_positive";
384
+ confidence = 90;
385
+ method = "usage_analysis";
386
+ reasons.push("Import is actually used");
387
+ if (usageCheck.usageType) {
388
+ reasons.push(`Usage type: ${usageCheck.usageType}`);
389
+ }
390
+ }
391
+ else {
392
+ status = "confirmed";
393
+ confidence = 95;
394
+ method = "usage_analysis";
395
+ reasons.push("Import is truly unused");
396
+ reasons.push("No references found in file");
397
+ }
398
+ break;
399
+ }
400
+ case "nonExistentMethod": {
401
+ // First check: does the method exist in the project at all?
402
+ const methodNameMatch = issue.message.match(/Method '([^']+)'/);
403
+ const objectNameMatch = issue.message.match(/on '([^']+)'/);
404
+ const methodName = methodNameMatch?.[1];
405
+ const objectName = objectNameMatch?.[1];
406
+ if (methodName) {
407
+ // Check if method exists with matching scope
408
+ const methodExists = checkMethodExistsInProject(methodName, objectName, ctx);
409
+ if (methodExists.exists) {
410
+ status = "false_positive";
411
+ confidence = 90;
412
+ method = "symbol_lookup";
413
+ reasons.push(`Method '${methodName}' exists in project at ${methodExists.location}`);
414
+ if (methodExists.scope) {
415
+ reasons.push(`Method is defined on object: ${methodExists.scope}`);
416
+ }
417
+ reasons.push("This is not a hallucination - the method exists");
418
+ break;
419
+ }
420
+ }
421
+ // Fallback: check inheritance chain (for class-based methods)
422
+ const inheritanceCheck = await checkInheritanceChain(issue, ctx);
423
+ if (inheritanceCheck.foundInParent) {
424
+ status = "false_positive";
425
+ confidence = 85;
426
+ method = "inheritance_analysis";
427
+ reasons.push("Method found in parent class or interface");
428
+ reasons.push("This is not a hallucination");
429
+ }
430
+ else {
431
+ status = "confirmed";
432
+ confidence = 80;
433
+ method = "inheritance_analysis";
434
+ reasons.push("Method not found in class hierarchy");
435
+ reasons.push("May be a dynamic method (not verifiable)");
436
+ }
437
+ break;
438
+ }
439
+ default: {
440
+ status = issue.confidence && issue.confidence > 90 ? "confirmed" : "uncertain";
441
+ confidence = issue.confidence || 50;
442
+ method = "fallback";
443
+ reasons.push("Using original confidence score");
444
+ reasons.push("No specific verification available for this issue type");
445
+ }
446
+ }
447
+ return {
448
+ original: issue,
449
+ status,
450
+ confidence,
451
+ reasons,
452
+ verificationMethod: method,
453
+ };
454
+ }
455
+ // ============================================================================
456
+ // Dead Code Verification with Caching
457
+ // ============================================================================
458
+ async function verifyDeadCodeWithCache(issue, ctx, cache) {
459
+ const reasons = [];
460
+ let confidence = 0;
461
+ let status = "uncertain";
462
+ let method = "";
463
+ switch (issue.type) {
464
+ case "unusedExport": {
465
+ // Highest-signal guard: if the symbol graph shows *any* downstream usage,
466
+ // this is not dead code. This prevents the exact class of false positives
467
+ // where the dead-code scan misses a semantic consumer, but the dependency
468
+ // tracer can prove a call chain exists.
469
+ const dep = getDependencyEvidenceForSymbol(issue.name, ctx, 2);
470
+ if (dep?.isUsed) {
471
+ status = "false_positive";
472
+ confidence = 98;
473
+ method = "dependency_graph";
474
+ const sampleFiles = dep.blast.affectedFiles
475
+ .slice(0, 4)
476
+ .map((f) => f)
477
+ .join(", ");
478
+ reasons.push(`Dependency graph shows '${issue.name}' is used by ${dep.blast.affectedFiles.length} file(s).`);
479
+ if (sampleFiles) {
480
+ reasons.push(`Example consumers: ${sampleFiles}${dep.blast.affectedFiles.length > 4 ? ", …" : ""}`);
481
+ }
482
+ reasons.push("Not dead code — keep this export or refactor with care");
483
+ break;
484
+ }
485
+ // Use cached future feature detection
486
+ const futureFeatureCheck = await detectFutureFeatureWithCache(issue, ctx, cache);
487
+ if (futureFeatureCheck.isFutureFeature) {
488
+ status = "false_positive";
489
+ confidence = futureFeatureCheck.confidence;
490
+ method = "future_feature_detection";
491
+ reasons.push("This export appears to be part of an incomplete/new feature:");
492
+ reasons.push(...futureFeatureCheck.reasons);
493
+ reasons.push("Not dead code - will likely be used when feature is complete");
494
+ break;
495
+ }
496
+ const isPublicApi = await checkIfPublicApi(issue, ctx);
497
+ const isTestFile = issue.file.includes('.test.') || issue.file.includes('.spec.') || issue.file.includes('/test/');
498
+ if (isPublicApi) {
499
+ status = "false_positive";
500
+ confidence = 88;
501
+ method = "api_analysis";
502
+ reasons.push("This appears to be a public API export");
503
+ reasons.push("External consumers may use this");
504
+ }
505
+ else if (isTestFile) {
506
+ status = "false_positive";
507
+ confidence = 75;
508
+ method = "test_file_analysis";
509
+ reasons.push("This is a test file - exports may be used by test runners");
510
+ }
511
+ else {
512
+ status = "confirmed";
513
+ confidence = 92;
514
+ method = "dead_code_detector_trust";
515
+ reasons.push("Dead code detector performed thorough cross-file analysis");
516
+ reasons.push("No imports, type references, or runtime usages found");
517
+ reasons.push("No indicators of planned/incomplete feature");
518
+ reasons.push("This is confirmed dead code");
519
+ }
520
+ break;
521
+ }
522
+ case "unusedFunction": {
523
+ const indirectCheck = await checkIndirectUsage(issue, ctx);
524
+ if (indirectCheck.isUsed) {
525
+ status = "false_positive";
526
+ confidence = 85;
527
+ method = "indirect_usage_analysis";
528
+ reasons.push("Function has indirect usage");
529
+ reasons.push(`Usage pattern: ${indirectCheck.pattern}`);
530
+ }
531
+ else {
532
+ status = "confirmed";
533
+ confidence = 88;
534
+ method = "static_analysis";
535
+ reasons.push("No direct or indirect usages found");
536
+ reasons.push("This is likely dead code");
537
+ }
538
+ break;
539
+ }
540
+ case "orphanedFile": {
541
+ const importCheck = await checkFileImports(issue, ctx);
542
+ if (importCheck.isImported) {
543
+ status = "false_positive";
544
+ confidence = 95;
545
+ method = "import_analysis";
546
+ reasons.push(`File is imported by ${importCheck.importers.length} file(s)`);
547
+ reasons.push("This is not an orphaned file");
548
+ }
549
+ else {
550
+ const isEntryPoint = await checkIfEntryPoint(issue, ctx);
551
+ if (isEntryPoint) {
552
+ status = "false_positive";
553
+ confidence = 90;
554
+ method = "entry_point_analysis";
555
+ reasons.push("This appears to be an entry point or config file");
556
+ reasons.push("Not expected to be imported");
557
+ }
558
+ else {
559
+ status = "confirmed";
560
+ confidence = 85;
561
+ method = "import_analysis";
562
+ reasons.push("No imports found");
563
+ reasons.push("Not a recognized entry point");
564
+ reasons.push("This may be an orphaned file");
565
+ }
566
+ }
567
+ break;
568
+ }
569
+ default: {
570
+ status = "uncertain";
571
+ confidence = 50;
572
+ method = "fallback";
573
+ reasons.push("Unknown dead code type");
574
+ }
575
+ }
576
+ return {
577
+ original: issue,
578
+ status,
579
+ confidence,
580
+ reasons,
581
+ verificationMethod: method,
582
+ };
583
+ }
584
+ async function detectFutureFeatureWithCache(issue, ctx, cache) {
585
+ const reasons = [];
586
+ let signalCount = 0;
587
+ const signals = [];
588
+ // Signal 1: Check git status (cached)
589
+ if (cache.gitStatus?.isNew || cache.gitStatus?.isModified) {
590
+ signalCount++;
591
+ signals.push("uncommitted changes");
592
+ reasons.push(`File has uncommitted ${cache.gitStatus.isNew ? "changes (new file)" : "modifications"}`);
593
+ }
594
+ // Signal 2: Check for TODO/FIXME comments (lazy load & cache)
595
+ if (cache.hasTodoComments === undefined) {
596
+ cache.hasTodoComments = await checkForTodoComments(issue.file, ctx, cache);
597
+ }
598
+ if (cache.hasTodoComments) {
599
+ signalCount++;
600
+ signals.push("todo comments");
601
+ reasons.push("File contains TODO/FIXME comments indicating planned work");
602
+ }
603
+ // Signal 3: Check if this is a stub implementation (lazy load & cache)
604
+ if (cache.isStub === undefined) {
605
+ cache.isStub = await checkIfStubImplementation(issue.file, issue, ctx, cache);
606
+ }
607
+ if (cache.isStub) {
608
+ signalCount++;
609
+ signals.push("stub implementation");
610
+ reasons.push("Code appears to be a stub/placeholder for future implementation");
611
+ }
612
+ // Signal 4: Check feature branch naming (cached)
613
+ if (cache.featureBranch) {
614
+ signalCount++;
615
+ signals.push("feature branch");
616
+ reasons.push("Working on a feature branch (not main/master)");
617
+ }
618
+ // Signal 5: Check if the referenced symbol looks like a planned feature
619
+ const symbolName = extractSymbolName(issue);
620
+ if (symbolName && looksLikePlannedFeature(symbolName)) {
621
+ signalCount += 0.5;
622
+ signals.push("planned naming");
623
+ reasons.push(`Symbol name "${symbolName}" suggests planned functionality`);
624
+ }
625
+ // Determine result based on signal strength
626
+ const isFutureFeature = signalCount >= 2 || (signalCount >= 1 && signals.includes("todo comments"));
627
+ const confidence = Math.min(95, 60 + signalCount * 15);
628
+ return {
629
+ isFutureFeature,
630
+ confidence,
631
+ reasons,
632
+ };
633
+ }
634
+ // ============================================================================
635
+ // Git Operations (Optimized)
636
+ // ============================================================================
637
+ async function checkGitAvailable(projectPath) {
638
+ try {
639
+ await execAsync("git rev-parse --git-dir", { cwd: projectPath });
640
+ return true;
641
+ }
642
+ catch {
643
+ return false;
644
+ }
645
+ }
646
+ /**
647
+ * Batch git status for ALL files in a single git call.
648
+ * Replaces per-file `git status --porcelain <file>` which spawned 100+ processes.
649
+ */
650
+ async function batchGitStatus(projectPath) {
651
+ const statusMap = new Map();
652
+ try {
653
+ const { stdout } = await execAsync("git status --porcelain", { cwd: projectPath, maxBuffer: 10 * 1024 * 1024 });
654
+ for (const line of stdout.split("\n")) {
655
+ if (!line || line.length < 4)
656
+ continue;
657
+ const statusCode = line.substring(0, 2);
658
+ const filePart = line.substring(3).trim();
659
+ if (!filePart)
660
+ continue;
661
+ const absPath = path.isAbsolute(filePart) ? filePart : path.join(projectPath, filePart);
662
+ statusMap.set(absPath, {
663
+ isNew: statusCode === "??" || statusCode.startsWith("A"),
664
+ isModified: statusCode.includes("M"),
665
+ });
666
+ }
667
+ }
668
+ catch {
669
+ // git not available or error — return empty map
670
+ }
671
+ return statusMap;
672
+ }
673
+ async function checkFeatureBranch(ctx) {
674
+ if (!ctx.gitAvailable)
675
+ return false;
676
+ try {
677
+ const { stdout } = await execAsync("git branch --show-current", { cwd: ctx.projectPath });
678
+ const branchName = stdout.trim();
679
+ const featurePatterns = [
680
+ /^feature\//i,
681
+ /^feat\//i,
682
+ /^wip\//i,
683
+ /^wip-/i,
684
+ /-wip$/i,
685
+ /^new\//i,
686
+ /^implement/i,
687
+ /^add-/i,
688
+ ];
689
+ return featurePatterns.some((pattern) => pattern.test(branchName));
690
+ }
691
+ catch {
692
+ return false;
693
+ }
694
+ }
695
+ // ============================================================================
696
+ // File Content Checks
697
+ // ============================================================================
698
+ async function checkForTodoComments(filePath, ctx, cache) {
699
+ if (!filePath)
700
+ return false;
701
+ try {
702
+ // Use cached content if available, otherwise load and cache
703
+ if (cache && !cache.content) {
704
+ cache.content = await fs.readFile(filePath, "utf-8");
705
+ }
706
+ const content = cache?.content ?? await fs.readFile(filePath, "utf-8");
707
+ const todoPatterns = [
708
+ /\/\/\s*TODO/i,
709
+ /\/\/\s*FIXME/i,
710
+ /\/\/\s*XXX/i,
711
+ /\/\*\s*TODO/i,
712
+ /\/\*\s*FIXME/i,
713
+ /#\s*TODO/i,
714
+ /#\s*FIXME/i,
715
+ /\{\s*\/\*\s*TODO/i,
716
+ ];
717
+ return todoPatterns.some((pattern) => pattern.test(content));
718
+ }
719
+ catch {
720
+ return false;
721
+ }
722
+ }
723
+ async function checkIfStubImplementation(filePath, issue, ctx, cache) {
724
+ if (!filePath)
725
+ return false;
726
+ try {
727
+ // Use cached content if available, otherwise load and cache
728
+ if (cache && !cache.content) {
729
+ cache.content = await fs.readFile(filePath, "utf-8");
730
+ }
731
+ const content = cache?.content ?? await fs.readFile(filePath, "utf-8");
732
+ const stubPatterns = [
733
+ /throw\s+new\s+Error\s*\(\s*["']\s*not\s+implemented/i,
734
+ /throw\s+new\s+Error\s*\(\s*["']\s*TODO/i,
735
+ /\/\/\s*TODO.*implement/i,
736
+ /return\s+null\s*;\s*\/\/\s*TODO/i,
737
+ /\/\/\s*placeholder/i,
738
+ /\/\/\s*stub/i,
739
+ ];
740
+ return stubPatterns.some((pattern) => pattern.test(content));
741
+ }
742
+ catch {
743
+ return false;
744
+ }
745
+ }
746
+ function extractSymbolName(issue) {
747
+ if ("message" in issue) {
748
+ const match = issue.message.match(/'([^']+)'/);
749
+ return match?.[1] || null;
750
+ }
751
+ return null;
752
+ }
753
+ function looksLikePlannedFeature(symbolName) {
754
+ const plannedPatterns = [
755
+ /new/i,
756
+ /upcoming/i,
757
+ /planned/i,
758
+ /future/i,
759
+ /next/i,
760
+ /v\d+/i,
761
+ /version\d+/i,
762
+ ];
763
+ return plannedPatterns.some((pattern) => pattern.test(symbolName));
764
+ }
765
+ // ============================================================================
766
+ // Local Scope Checks (prevent verifier from confirming local vars as hallucinations)
767
+ // ============================================================================
768
+ async function checkSymbolDefinedLocally(issue, ctx, cache) {
769
+ const symbolName = extractSymbolName(issue);
770
+ if (!symbolName || !issue.file)
771
+ return { isDefined: false };
772
+ try {
773
+ // Load file content once (cache)
774
+ if (!cache.content) {
775
+ cache.content = await fs.readFile(issue.file, "utf-8");
776
+ }
777
+ // Parse local symbols from the file itself (includes params when enabled)
778
+ const fileLang = detectFileLanguage(issue.file, ctx.language);
779
+ const symbols = extractSymbolsAST(cache.content, issue.file, fileLang, {
780
+ includeParameterSymbols: true,
781
+ });
782
+ const found = symbols.some((s) => s.name === symbolName);
783
+ return found ? { isDefined: true, hint: "local symbol table" } : { isDefined: false };
784
+ }
785
+ catch {
786
+ return { isDefined: false };
787
+ }
788
+ }
789
+ // ============================================================================
790
+ // Symbol & Module Checks
791
+ // ============================================================================
792
+ function checkSymbolExistsInProject(issue, ctx) {
793
+ const symbolName = issue.message.match(/'([^']+)'/)?.[1];
794
+ if (!symbolName)
795
+ return { exists: false };
796
+ for (const [name, definitions] of ctx.projectContext.symbolIndex) {
797
+ if (name === symbolName || name.toLowerCase() === symbolName.toLowerCase()) {
798
+ return {
799
+ exists: true,
800
+ location: definitions[0]?.file || "unknown",
801
+ };
802
+ }
803
+ }
804
+ return { exists: false };
805
+ }
806
+ /**
807
+ * Check if a method exists in the project, optionally with a specific scope (object name).
808
+ * This handles object literal methods like: const api = { method: () => {} }
809
+ */
810
+ function checkMethodExistsInProject(methodName, objectName, ctx) {
811
+ // Search through all symbols in the project context
812
+ for (const [name, definitions] of ctx.projectContext.symbolIndex) {
813
+ if (name === methodName) {
814
+ // Check all definitions for this method name
815
+ for (const def of definitions) {
816
+ // Check if it's a method type
817
+ if (def.symbol.kind === "method") {
818
+ // If object name is provided, check scope matches
819
+ if (!objectName || !def.symbol.scope || def.symbol.scope === objectName) {
820
+ return {
821
+ exists: true,
822
+ location: def.file || "unknown",
823
+ scope: def.symbol.scope,
824
+ };
825
+ }
826
+ }
827
+ }
828
+ }
829
+ }
830
+ return { exists: false };
831
+ }
832
+ async function checkModuleExports(issue, ctx) {
833
+ const moduleMatch = issue.message.match(/module ['"]([^'"]+)['"]/);
834
+ const exportMatch = issue.message.match(/export ['"]([^'"]+)['"]/);
835
+ const moduleName = moduleMatch?.[1];
836
+ const exportName = exportMatch?.[1];
837
+ if (!moduleName) {
838
+ return { moduleExists: false, exportExists: false };
839
+ }
840
+ // Convert Python dotted module paths to file paths (e.g., app.api.cli_tokens -> app/api/cli_tokens)
841
+ const moduleAsPath = moduleName.replace(/\./g, "/");
842
+ const possiblePaths = [
843
+ path.join(ctx.projectPath, moduleName),
844
+ path.join(ctx.projectPath, `${moduleName}.ts`),
845
+ path.join(ctx.projectPath, `${moduleName}.tsx`),
846
+ path.join(ctx.projectPath, `${moduleName}.js`),
847
+ path.join(ctx.projectPath, `${moduleName}/index.ts`),
848
+ path.join(ctx.projectPath, `${moduleName}/index.js`),
849
+ // Python module paths (dotted notation)
850
+ path.join(ctx.projectPath, `${moduleAsPath}.py`),
851
+ path.join(ctx.projectPath, moduleAsPath, "__init__.py"),
852
+ ];
853
+ let moduleExists = false;
854
+ let modulePath = "";
855
+ for (const p of possiblePaths) {
856
+ try {
857
+ await fs.access(p);
858
+ moduleExists = true;
859
+ modulePath = p;
860
+ break;
861
+ }
862
+ catch {
863
+ // Continue checking
864
+ }
865
+ }
866
+ if (!moduleExists || !exportName) {
867
+ return { moduleExists, exportExists: false };
868
+ }
869
+ try {
870
+ const content = await fs.readFile(modulePath, "utf-8");
871
+ const isPython = modulePath.endsWith(".py");
872
+ const exportPatterns = [
873
+ new RegExp(`export\\s+(?:const|let|var|function|class|interface|type)\\s+${exportName}\\b`),
874
+ new RegExp(`export\\s*\\{[^}]*\\b${exportName}\\b`),
875
+ new RegExp(`export\\s+default\\s+(?:class|function)?\\s*\\b${exportName}\\b`),
876
+ ];
877
+ if (isPython) {
878
+ // Python: check for class/def/variable definitions
879
+ exportPatterns.push(new RegExp(`^class\\s+${exportName}\\b`, "m"), new RegExp(`^def\\s+${exportName}\\b`, "m"), new RegExp(`^${exportName}\\s*=`, "m"));
880
+ // Python: check for module-level imports (re-imports are valid namespace members)
881
+ exportPatterns.push(new RegExp(`^from\\s+\\S+\\s+import\\s+.*\\b${exportName}\\b`, "m"), new RegExp(`^import\\s+.*\\b${exportName}\\b`, "m"));
882
+ }
883
+ const exportExists = exportPatterns.some((pattern) => pattern.test(content));
884
+ return { moduleExists: true, exportExists };
885
+ }
886
+ catch {
887
+ return { moduleExists: true, exportExists: false };
888
+ }
889
+ }
890
+ async function checkPackageExistsOnRegistry(pkgName, language) {
891
+ if (!pkgName)
892
+ return false;
893
+ const commonPackages = new Set([
894
+ "react", "react-dom", "vue", "angular", "svelte",
895
+ "lodash", "underscore", "ramda",
896
+ "axios", "fetch", "node-fetch",
897
+ "express", "koa", "fastify", "hapi",
898
+ "jest", "mocha", "jasmine", "vitest", "playwright",
899
+ "typescript", "ts-node", "tsx",
900
+ "webpack", "vite", "rollup", "esbuild", "parcel",
901
+ "eslint", "prettier", "babel",
902
+ "mongoose", "sequelize", "prisma", "typeorm",
903
+ "mongodb", "redis", "pg", "mysql",
904
+ "jsonwebtoken", "bcrypt", "passport",
905
+ "winston", "pino", "morgan",
906
+ "dotenv", "cross-env", "rimraf",
907
+ "fs-extra", "glob", "minimatch",
908
+ "chalk", "commander", "inquirer",
909
+ "dayjs", "date-fns", "moment",
910
+ "uuid", "nanoid", "cuid",
911
+ "zod", "yup", "joi", "class-validator",
912
+ "tailwindcss", "styled-components", "emotion",
913
+ "@testing-library/react", "@testing-library/jest-dom",
914
+ "@types/node", "@types/react", "@types/express",
915
+ "next", "nuxt", "gatsby",
916
+ ]);
917
+ if (commonPackages.has(pkgName.toLowerCase()))
918
+ return true;
919
+ if (pkgName.startsWith("@")) {
920
+ const scope = pkgName.split("/")[0];
921
+ const name = pkgName.split("/")[1];
922
+ if (!name)
923
+ return false;
924
+ const commonScopes = ["@types", "@babel", "@rollup", "@vitejs", "@nestjs", "@angular", "@mui"];
925
+ if (commonScopes.includes(scope))
926
+ return true;
927
+ }
928
+ // Fall back to real registry check (cached + fail-open on network errors)
929
+ return checkPackageRegistry(pkgName, language);
930
+ }
931
+ function extractPackageName(message) {
932
+ const match = message.match(/Package ['"]([^'"]+)['"]/);
933
+ return match?.[1] || "";
934
+ }
935
+ /**
936
+ * Check if a package exists in any subdirectory's package.json.
937
+ * This catches cases where the main manifest loader missed subdirectory manifests
938
+ * (e.g., monorepo-style projects with frontend/package.json + backend/package.json).
939
+ */
940
+ async function checkPackageInSubdirectoryManifest(pkgName, projectPath) {
941
+ const COMMON_SUBDIRS = ["frontend", "backend", "client", "server", "app", "web", "api"];
942
+ for (const subdir of COMMON_SUBDIRS) {
943
+ const pkgJsonPath = path.join(projectPath, subdir, "package.json");
944
+ try {
945
+ const content = await fs.readFile(pkgJsonPath, "utf-8");
946
+ const pkg = JSON.parse(content);
947
+ const allDeps = {
948
+ ...pkg.dependencies,
949
+ ...pkg.devDependencies,
950
+ ...pkg.peerDependencies,
951
+ };
952
+ if (pkgName in allDeps) {
953
+ return true;
954
+ }
955
+ // Also check @types/ mapping
956
+ if (`@types/${pkgName}` in (pkg.devDependencies || {})) {
957
+ return true;
958
+ }
959
+ }
960
+ catch {
961
+ // File doesn't exist or can't be parsed
962
+ }
963
+ }
964
+ return false;
965
+ }
966
+ async function verifyParameterCount(issue, ctx) {
967
+ const match = issue.message.match(/expects (\d+) args, got (\d+)/);
968
+ if (!match) {
969
+ return { isMismatch: true };
970
+ }
971
+ const expected = parseInt(match[1], 10);
972
+ const actual = parseInt(match[2], 10);
973
+ const symbolName = issue.message.match(/Function ['"]([^'"]+)['"]/)?.[1];
974
+ if (symbolName) {
975
+ const definitions = ctx.projectContext.symbolIndex.get(symbolName);
976
+ if (definitions && definitions.length > 0) {
977
+ const def = definitions[0];
978
+ if (def.symbol.params?.some((p) => p.name.startsWith("..."))) {
979
+ return { isMismatch: false, expected, actual };
980
+ }
981
+ }
982
+ }
983
+ return { isMismatch: true, expected, actual };
984
+ }
985
+ async function checkImportUsage(issue, ctx) {
986
+ const importName = issue.message.match(/'([^']+)'/)?.[1];
987
+ if (!importName || !issue.file)
988
+ return { isUsed: false };
989
+ try {
990
+ let rawContent = ctx.fileContentCache.get(issue.file);
991
+ if (!rawContent) {
992
+ rawContent = await fs.readFile(issue.file, "utf-8");
993
+ ctx.fileContentCache.set(issue.file, rawContent);
994
+ }
995
+ // Strip import lines, comment-only lines, and the definition line so they
996
+ // don't count as "usage".
997
+ // Previously, `import { ChefHat } from 'lucide-react'; // ChefHat is unused`
998
+ // produced 2 regex matches (import + comment) and was falsely marked as "used".
999
+ //
1000
+ // For local dead code findings (e.g., `const GHOST_REGISTRY_ID = ...` or
1001
+ // `function deprecatedAuditLog()`), the definition line itself contains the
1002
+ // symbol name. Without excluding it, checkImportUsage falsely returns
1003
+ // isUsed=true because the regex matches the definition.
1004
+ const escapedName = importName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1005
+ const issueLine = issue.line; // 1-indexed line number of the finding
1006
+ const content = rawContent
1007
+ .split("\n")
1008
+ .filter((line, index) => {
1009
+ // Exclude the definition/declaration line itself (prevents local dead code
1010
+ // findings like GHOST_REGISTRY_ID from matching their own definition)
1011
+ if (issueLine && index + 1 === issueLine)
1012
+ return false;
1013
+ const trimmed = line.trim();
1014
+ // Exclude import lines
1015
+ if (trimmed.startsWith("import ") || trimmed.startsWith("import{"))
1016
+ return false;
1017
+ // Exclude from...import lines (Python)
1018
+ if (trimmed.startsWith("from "))
1019
+ return false;
1020
+ // Exclude comment-only lines (JS/TS/Python)
1021
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*") || trimmed.startsWith("*"))
1022
+ return false;
1023
+ return true;
1024
+ })
1025
+ .join("\n");
1026
+ const patterns = [
1027
+ { regex: new RegExp(`\\b${escapedName}\\s*\\(`, "g"), type: "function call" },
1028
+ { regex: new RegExp(`\\b${escapedName}\\.`, "g"), type: "property access" },
1029
+ { regex: new RegExp(`<${escapedName}[\\s/>]`, "g"), type: "JSX component" },
1030
+ { regex: new RegExp(`\\b${escapedName}\\b`, "g"), type: "reference" },
1031
+ { regex: new RegExp(`type\\s+\\w+.*\\b${escapedName}\\b`, "g"), type: "type usage" },
1032
+ { regex: new RegExp(`as\\s+${escapedName}\\b`, "g"), type: "type assertion" },
1033
+ ];
1034
+ for (const { regex, type } of patterns) {
1035
+ const matches = content.match(regex);
1036
+ if (matches && matches.length >= 1) {
1037
+ return { isUsed: true, usageType: type };
1038
+ }
1039
+ }
1040
+ return { isUsed: false };
1041
+ }
1042
+ catch {
1043
+ return { isUsed: false };
1044
+ }
1045
+ }
1046
+ async function checkInheritanceChain(issue, ctx) {
1047
+ return { foundInParent: false };
1048
+ }
1049
+ async function checkIfPublicApi(issue, ctx) {
1050
+ const publicPatterns = [
1051
+ /index\.ts$/,
1052
+ /index\.js$/,
1053
+ /\bapi\b/,
1054
+ /\bpublic\b/,
1055
+ /\bexports\b/,
1056
+ /\blib\b/,
1057
+ ];
1058
+ return publicPatterns.some((pattern) => pattern.test(issue.file));
1059
+ }
1060
+ async function checkIndirectUsage(issue, ctx) {
1061
+ const functionName = issue.name;
1062
+ const filePath = issue.file;
1063
+ try {
1064
+ const content = await fs.readFile(filePath, "utf-8");
1065
+ const patterns = [
1066
+ { regex: new RegExp(`\\b${functionName}\\b\\s*[,})\\]]`, "g"), pattern: "callback/collection" },
1067
+ { regex: new RegExp(`['"]\\b${functionName}\\b['"]`, "g"), pattern: "string reference" },
1068
+ { regex: new RegExp(`\\.\\b${functionName}\\b`, "g"), pattern: "method assignment" },
1069
+ ];
1070
+ for (const { regex, pattern } of patterns) {
1071
+ if (regex.test(content)) {
1072
+ return { isUsed: true, pattern };
1073
+ }
1074
+ }
1075
+ return { isUsed: false };
1076
+ }
1077
+ catch {
1078
+ return { isUsed: false };
1079
+ }
1080
+ }
1081
+ async function checkFileImports(issue, ctx) {
1082
+ // Use in-memory project context instead of reading every file from disk.
1083
+ // The reverseImportGraph + raw import data is already available and up-to-date.
1084
+ const importers = [];
1085
+ const fileName = issue.name || issue.file;
1086
+ // 1. Check reverseImportGraph (resolved imports)
1087
+ const reverseImportGraph = ctx.projectContext.reverseImportGraph;
1088
+ if (reverseImportGraph && typeof reverseImportGraph[Symbol.iterator] === "function") {
1089
+ for (const [absPath, reverseList] of reverseImportGraph) {
1090
+ // Match by absolute path or by basename
1091
+ if (absPath === fileName ||
1092
+ absPath.endsWith(`/${fileName}`) ||
1093
+ path.basename(absPath).replace(/\.[^.]+$/, "") ===
1094
+ path.basename(fileName).replace(/\.[^.]+$/, "")) {
1095
+ importers.push(...reverseList);
1096
+ }
1097
+ }
1098
+ }
1099
+ if (importers.length > 0) {
1100
+ return { isImported: true, importers: [...new Set(importers)] };
1101
+ }
1102
+ // 2. Fallback: check raw import sources in context (catches unresolved imports)
1103
+ const baseName = path.basename(fileName).replace(/\.[^.]+$/, "");
1104
+ const files = ctx.projectContext.files;
1105
+ if (!files || typeof files[Symbol.iterator] !== "function") {
1106
+ return { isImported: false, importers: [] };
1107
+ }
1108
+ for (const [filePath, fileInfo] of files) {
1109
+ if (filePath === issue.file)
1110
+ continue;
1111
+ for (const imp of fileInfo.imports) {
1112
+ if (imp.source.includes(baseName)) {
1113
+ importers.push(filePath);
1114
+ break;
1115
+ }
1116
+ }
1117
+ }
1118
+ return { isImported: importers.length > 0, importers: [...new Set(importers)] };
1119
+ }
1120
+ async function checkIfEntryPoint(issue, ctx) {
1121
+ const entryPatterns = [
1122
+ /main\.(ts|js)$/,
1123
+ /index\.(ts|js)$/,
1124
+ /app\.(ts|js)$/,
1125
+ /server\.(ts|js)$/,
1126
+ /cli\.(ts|js)$/,
1127
+ /vite\.config\./,
1128
+ /webpack\.config\./,
1129
+ /next\.config\./,
1130
+ /tsup\.config\./,
1131
+ /rollup\.config\./,
1132
+ /jest\.config\./,
1133
+ /vitest\.config\./,
1134
+ /playwright\.config\./,
1135
+ /cypress\.config\./,
1136
+ /tailwind\.config\./,
1137
+ /postcss\.config\./,
1138
+ /eslint\.config\./,
1139
+ /prettier\.config\./,
1140
+ ];
1141
+ return entryPatterns.some((pattern) => pattern.test(issue.file));
1142
+ }
1143
+ // ============================================================================
1144
+ // Result Filtering Helpers
1145
+ // ============================================================================
1146
+ export function getConfirmedFindings(result) {
1147
+ // Include confirmed findings
1148
+ const confirmedHallucinations = result.confirmed
1149
+ .filter((f) => isValidationIssue(f.original))
1150
+ .map((f) => f.original);
1151
+ const confirmedDeadCode = result.confirmed
1152
+ .filter((f) => !isValidationIssue(f.original))
1153
+ .map((f) => f.original);
1154
+ // Also include high-confidence uncertain findings (confidence >= 85)
1155
+ // These are likely real issues that we couldn't fully verify
1156
+ const highConfidenceUncertainHallucinations = result.uncertain
1157
+ .filter((f) => f.confidence >= 85 && isValidationIssue(f.original))
1158
+ .map((f) => f.original);
1159
+ const highConfidenceUncertainDeadCode = result.uncertain
1160
+ .filter((f) => f.confidence >= 85 && !isValidationIssue(f.original))
1161
+ .map((f) => f.original);
1162
+ return {
1163
+ hallucinations: [...confirmedHallucinations, ...highConfidenceUncertainHallucinations],
1164
+ deadCode: [...confirmedDeadCode, ...highConfidenceUncertainDeadCode],
1165
+ };
1166
+ }
1167
+ // Keep backward compatibility - alias for the main function
1168
+ export { verifyFindingsAutomatically as verifyFindings };
1169
+ //# sourceMappingURL=findingVerifier.js.map