code-auditor-mcp 2.6.1 → 3.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 (347) hide show
  1. package/README.md +319 -147
  2. package/dist/analyzers/analyzerUtils.d.ts +13 -11
  3. package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
  4. package/dist/analyzers/analyzerUtils.js +22 -56
  5. package/dist/analyzers/analyzerUtils.js.map +1 -1
  6. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  7. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
  8. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
  9. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  10. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  11. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  12. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  13. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +1 -1
  14. package/dist/analyzers/dataAccessAnalyzer.js +50 -24
  15. package/dist/analyzers/dataAccessAnalyzer.js.map +1 -1
  16. package/dist/analyzers/documentationAnalyzer.d.ts +4 -1
  17. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  18. package/dist/analyzers/documentationAnalyzer.js +210 -111
  19. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  20. package/dist/analyzers/dryAnalyzer.d.ts.map +1 -1
  21. package/dist/analyzers/dryAnalyzer.js +190 -58
  22. package/dist/analyzers/dryAnalyzer.js.map +1 -1
  23. package/dist/analyzers/invariantsAnalyzer.d.ts +20 -0
  24. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -0
  25. package/dist/analyzers/invariantsAnalyzer.js +148 -0
  26. package/dist/analyzers/invariantsAnalyzer.js.map +1 -0
  27. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  28. package/dist/analyzers/reactAnalyzer.js +2 -2
  29. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  30. package/dist/analyzers/schemaAnalyzer.d.ts +2 -0
  31. package/dist/analyzers/schemaAnalyzer.d.ts.map +1 -1
  32. package/dist/analyzers/schemaAnalyzer.js +306 -147
  33. package/dist/analyzers/schemaAnalyzer.js.map +1 -1
  34. package/dist/analyzers/solidAnalyzer.d.ts.map +1 -1
  35. package/dist/analyzers/solidAnalyzer.js +145 -119
  36. package/dist/analyzers/solidAnalyzer.js.map +1 -1
  37. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +3 -1
  38. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  39. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +41 -1
  40. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  41. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  42. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +8 -8
  43. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  44. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  45. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +6 -6
  46. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  47. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  48. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  49. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +1 -1
  50. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  51. package/dist/api/index.js +1 -0
  52. package/dist/api/index.js.map +1 -1
  53. package/dist/auditRunner.d.ts.map +1 -1
  54. package/dist/auditRunner.js +367 -24
  55. package/dist/auditRunner.js.map +1 -1
  56. package/dist/cli.d.ts +1 -1
  57. package/dist/cli.d.ts.map +1 -1
  58. package/dist/cli.js +740 -18
  59. package/dist/cli.js.map +1 -1
  60. package/dist/codeIndexDB-enhanced.d.ts +35 -13
  61. package/dist/codeIndexDB-enhanced.d.ts.map +1 -1
  62. package/dist/codeIndexDB-enhanced.js +311 -214
  63. package/dist/codeIndexDB-enhanced.js.map +1 -1
  64. package/dist/codeIndexDB.d.ts +222 -0
  65. package/dist/codeIndexDB.d.ts.map +1 -0
  66. package/dist/codeIndexDB.js +2042 -0
  67. package/dist/codeIndexDB.js.map +1 -0
  68. package/dist/codeIndexService.d.ts.map +1 -1
  69. package/dist/codeIndexService.js.map +1 -1
  70. package/dist/componentScanner.d.ts +14 -8
  71. package/dist/componentScanner.d.ts.map +1 -1
  72. package/dist/componentScanner.js +136 -113
  73. package/dist/componentScanner.js.map +1 -1
  74. package/dist/config/configLoader.d.ts.map +1 -1
  75. package/dist/config/configLoader.js +10 -7
  76. package/dist/config/configLoader.js.map +1 -1
  77. package/dist/config/defaults.d.ts.map +1 -1
  78. package/dist/constants.js.map +1 -1
  79. package/dist/fingerprint.d.ts +15 -0
  80. package/dist/fingerprint.d.ts.map +1 -0
  81. package/dist/fingerprint.js +32 -0
  82. package/dist/fingerprint.js.map +1 -0
  83. package/dist/functionScanner.d.ts +5 -0
  84. package/dist/functionScanner.d.ts.map +1 -1
  85. package/dist/functionScanner.js +417 -341
  86. package/dist/functionScanner.js.map +1 -1
  87. package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
  88. package/dist/generators/AiderConfigGenerator.d.ts.map +1 -1
  89. package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
  90. package/dist/generators/BaseConfigGenerator.js.map +1 -1
  91. package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
  92. package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
  93. package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
  94. package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
  95. package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
  96. package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
  97. package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
  98. package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
  99. package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -1
  100. package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
  101. package/dist/grammars/tree-sitter-go.wasm +0 -0
  102. package/dist/grammars/tree-sitter-javascript.wasm +0 -0
  103. package/dist/grammars/tree-sitter-tsx.wasm +0 -0
  104. package/dist/grammars/tree-sitter-typescript.wasm +0 -0
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +2 -1
  108. package/dist/index.js.map +1 -1
  109. package/dist/invariants/index.d.ts +9 -0
  110. package/dist/invariants/index.d.ts.map +1 -0
  111. package/dist/invariants/index.js +4 -0
  112. package/dist/invariants/index.js.map +1 -0
  113. package/dist/invariants/invariant-rules.schema.json +137 -0
  114. package/dist/invariants/ruleEngine.d.ts +32 -0
  115. package/dist/invariants/ruleEngine.d.ts.map +1 -0
  116. package/dist/invariants/ruleEngine.js +536 -0
  117. package/dist/invariants/ruleEngine.js.map +1 -0
  118. package/dist/invariants/ruleValidator.d.ts +17 -0
  119. package/dist/invariants/ruleValidator.d.ts.map +1 -0
  120. package/dist/invariants/ruleValidator.js +264 -0
  121. package/dist/invariants/ruleValidator.js.map +1 -0
  122. package/dist/invariants/types.d.ts +78 -0
  123. package/dist/invariants/types.d.ts.map +1 -0
  124. package/dist/invariants/types.js +5 -0
  125. package/dist/invariants/types.js.map +1 -0
  126. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  127. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  128. package/dist/languages/LanguageRegistry.d.ts.map +1 -1
  129. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  130. package/dist/languages/RuntimeManager.js +1 -1
  131. package/dist/languages/RuntimeManager.js.map +1 -1
  132. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  133. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  134. package/dist/languages/adapterBridge.d.ts +102 -0
  135. package/dist/languages/adapterBridge.d.ts.map +1 -0
  136. package/dist/languages/adapterBridge.js +382 -0
  137. package/dist/languages/adapterBridge.js.map +1 -0
  138. package/dist/languages/go/GoAdapter.d.ts +37 -26
  139. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  140. package/dist/languages/go/GoAdapter.js +560 -281
  141. package/dist/languages/go/GoAdapter.js.map +1 -1
  142. package/dist/languages/index.d.ts +5 -2
  143. package/dist/languages/index.d.ts.map +1 -1
  144. package/dist/languages/index.js +11 -9
  145. package/dist/languages/index.js.map +1 -1
  146. package/dist/languages/tree-sitter/converter.d.ts +43 -0
  147. package/dist/languages/tree-sitter/converter.d.ts.map +1 -0
  148. package/dist/languages/tree-sitter/converter.js +150 -0
  149. package/dist/languages/tree-sitter/converter.js.map +1 -0
  150. package/dist/languages/tree-sitter/parser.d.ts +36 -0
  151. package/dist/languages/tree-sitter/parser.d.ts.map +1 -0
  152. package/dist/languages/tree-sitter/parser.js +114 -0
  153. package/dist/languages/tree-sitter/parser.js.map +1 -0
  154. package/dist/languages/types.d.ts +11 -0
  155. package/dist/languages/types.d.ts.map +1 -1
  156. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +92 -0
  157. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -0
  158. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +951 -0
  159. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -0
  160. package/dist/mcp-tools/projectTasks.d.ts +80 -11
  161. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  162. package/dist/mcp-tools/projectTasks.js +279 -13
  163. package/dist/mcp-tools/projectTasks.js.map +1 -1
  164. package/dist/mcp-tools/whitelistTools.d.ts +21 -21
  165. package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
  166. package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
  167. package/dist/mcp-tools/workflowGuide.js +130 -126
  168. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  169. package/dist/mcp-tools-shared.d.ts.map +1 -1
  170. package/dist/mcp-tools-shared.js.map +1 -1
  171. package/dist/mcp-ui-simple.js +1 -1
  172. package/dist/mcp-ui-simple.js.map +1 -1
  173. package/dist/mcp.d.ts +1 -1
  174. package/dist/mcp.d.ts.map +1 -1
  175. package/dist/mcp.js +1455 -1132
  176. package/dist/mcp.js.map +1 -1
  177. package/dist/mcpAuditJobs.d.ts +4 -0
  178. package/dist/mcpAuditJobs.d.ts.map +1 -1
  179. package/dist/mcpAuditJobs.js +49 -2
  180. package/dist/mcpAuditJobs.js.map +1 -1
  181. package/dist/mcpDiagnostics.d.ts.map +1 -1
  182. package/dist/mcpDiagnostics.js +8 -5
  183. package/dist/mcpDiagnostics.js.map +1 -1
  184. package/dist/mcpToolErrors.d.ts.map +1 -1
  185. package/dist/mcpToolErrors.js.map +1 -1
  186. package/dist/native/ast-grep-napi.darwin-arm64.node +0 -0
  187. package/dist/native/ast-grep-napi.darwin-x64.node +0 -0
  188. package/dist/native/ast-grep-napi.linux-arm64-gnu.node +0 -0
  189. package/dist/native/ast-grep-napi.linux-arm64-musl.node +0 -0
  190. package/dist/native/ast-grep-napi.linux-x64-gnu.node +0 -0
  191. package/dist/native/ast-grep-napi.linux-x64-musl.node +0 -0
  192. package/dist/native/ast-grep-napi.win32-x64-msvc.node +0 -0
  193. package/dist/native-bootstrap.d.ts +15 -0
  194. package/dist/native-bootstrap.d.ts.map +1 -0
  195. package/dist/native-bootstrap.js +75 -0
  196. package/dist/native-bootstrap.js.map +1 -0
  197. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  198. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  199. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  200. package/dist/reporting/reportGenerator.d.ts.map +1 -1
  201. package/dist/reporting/reportGenerator.js +4 -0
  202. package/dist/reporting/reportGenerator.js.map +1 -1
  203. package/dist/reporting/sarifReportGenerator.d.ts +39 -0
  204. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -0
  205. package/dist/reporting/sarifReportGenerator.js +229 -0
  206. package/dist/reporting/sarifReportGenerator.js.map +1 -0
  207. package/dist/search/QueryParser.d.ts +41 -0
  208. package/dist/search/QueryParser.d.ts.map +1 -1
  209. package/dist/search/QueryParser.js +200 -19
  210. package/dist/search/QueryParser.js.map +1 -1
  211. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  212. package/dist/services/CodeMapGenerator.js +4 -4
  213. package/dist/services/CodeMapGenerator.js.map +1 -1
  214. package/dist/services/ProjectTaskRepository.d.ts +34 -9
  215. package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
  216. package/dist/services/ProjectTaskRepository.js +385 -10
  217. package/dist/services/ProjectTaskRepository.js.map +1 -1
  218. package/dist/services/SchemaParser.d.ts.map +1 -1
  219. package/dist/services/SchemaParser.js +5 -8
  220. package/dist/services/SchemaParser.js.map +1 -1
  221. package/dist/services/auditJobService.d.ts +1 -1
  222. package/dist/services/projectTaskHelpers.js.map +1 -1
  223. package/dist/services/whitelistService.d.ts.map +1 -1
  224. package/dist/services/whitelistService.js +1 -1
  225. package/dist/services/whitelistService.js.map +1 -1
  226. package/dist/tool-registry.d.ts +79 -0
  227. package/dist/tool-registry.d.ts.map +1 -0
  228. package/dist/tool-registry.js +139 -0
  229. package/dist/tool-registry.js.map +1 -0
  230. package/dist/types/projectTask.d.ts +31 -0
  231. package/dist/types/projectTask.d.ts.map +1 -1
  232. package/dist/types.d.ts +26 -2
  233. package/dist/types.d.ts.map +1 -1
  234. package/dist/types.js.map +1 -1
  235. package/dist/ui/InteractivePrompts.d.ts.map +1 -1
  236. package/dist/ui/InteractivePrompts.js.map +1 -1
  237. package/dist/utils/astParser.d.ts +31 -38
  238. package/dist/utils/astParser.d.ts.map +1 -1
  239. package/dist/utils/astParser.js +138 -196
  240. package/dist/utils/astParser.js.map +1 -1
  241. package/dist/utils/astUtils.d.ts +87 -58
  242. package/dist/utils/astUtils.d.ts.map +1 -1
  243. package/dist/utils/astUtils.js +852 -581
  244. package/dist/utils/astUtils.js.map +1 -1
  245. package/dist/utils/componentPatterns.js.map +1 -1
  246. package/dist/utils/componentResponsibility.d.ts +13 -9
  247. package/dist/utils/componentResponsibility.d.ts.map +1 -1
  248. package/dist/utils/componentResponsibility.js +236 -206
  249. package/dist/utils/componentResponsibility.js.map +1 -1
  250. package/dist/utils/dependencyExtractor.d.ts +11 -6
  251. package/dist/utils/dependencyExtractor.d.ts.map +1 -1
  252. package/dist/utils/dependencyExtractor.js +200 -119
  253. package/dist/utils/dependencyExtractor.js.map +1 -1
  254. package/dist/utils/fileDiscovery.d.ts +1 -1
  255. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  256. package/dist/utils/fileDiscovery.js.map +1 -1
  257. package/dist/utils/jsDocParser.d.ts +27 -30
  258. package/dist/utils/jsDocParser.d.ts.map +1 -1
  259. package/dist/utils/jsDocParser.js +271 -207
  260. package/dist/utils/jsDocParser.js.map +1 -1
  261. package/dist/utils/reactDetection.d.ts +27 -12
  262. package/dist/utils/reactDetection.d.ts.map +1 -1
  263. package/dist/utils/reactDetection.js +331 -219
  264. package/dist/utils/reactDetection.js.map +1 -1
  265. package/dist/workers/auditWorker.js +17 -3
  266. package/dist/workers/auditWorker.js.map +1 -1
  267. package/package.json +54 -51
  268. package/dist/adapters/GoAdapter.d.ts +0 -53
  269. package/dist/adapters/GoAdapter.d.ts.map +0 -1
  270. package/dist/adapters/GoAdapter.js +0 -611
  271. package/dist/adapters/GoAdapter.js.map +0 -1
  272. package/dist/adapters/GoConfig.d.ts +0 -35
  273. package/dist/adapters/GoConfig.d.ts.map +0 -1
  274. package/dist/adapters/GoConfig.js +0 -155
  275. package/dist/adapters/GoConfig.js.map +0 -1
  276. package/dist/adapters/LanguageAdapter.d.ts +0 -135
  277. package/dist/adapters/LanguageAdapter.d.ts.map +0 -1
  278. package/dist/adapters/LanguageAdapter.js +0 -47
  279. package/dist/adapters/LanguageAdapter.js.map +0 -1
  280. package/dist/adapters/TypeScriptAdapter.d.ts +0 -43
  281. package/dist/adapters/TypeScriptAdapter.d.ts.map +0 -1
  282. package/dist/adapters/TypeScriptAdapter.js +0 -386
  283. package/dist/adapters/TypeScriptAdapter.js.map +0 -1
  284. package/dist/adapters/UniversalAnalyzer.d.ts +0 -42
  285. package/dist/adapters/UniversalAnalyzer.d.ts.map +0 -1
  286. package/dist/adapters/UniversalAnalyzer.js +0 -103
  287. package/dist/adapters/UniversalAnalyzer.js.map +0 -1
  288. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts +0 -48
  289. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts.map +0 -1
  290. package/dist/adapters/UniversalSOLIDAnalyzer.js +0 -178
  291. package/dist/adapters/UniversalSOLIDAnalyzer.js.map +0 -1
  292. package/dist/adapters/solidAnalyzerCompat.d.ts +0 -21
  293. package/dist/adapters/solidAnalyzerCompat.d.ts.map +0 -1
  294. package/dist/adapters/solidAnalyzerCompat.js +0 -78
  295. package/dist/adapters/solidAnalyzerCompat.js.map +0 -1
  296. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts +0 -20
  297. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts.map +0 -1
  298. package/dist/analyzers/dataAccessAnalyzerCompat.js +0 -59
  299. package/dist/analyzers/dataAccessAnalyzerCompat.js.map +0 -1
  300. package/dist/analyzers/documentationAnalyzerCompat.d.ts +0 -20
  301. package/dist/analyzers/documentationAnalyzerCompat.d.ts.map +0 -1
  302. package/dist/analyzers/documentationAnalyzerCompat.js +0 -52
  303. package/dist/analyzers/documentationAnalyzerCompat.js.map +0 -1
  304. package/dist/analyzers/dryAnalyzerCompat.d.ts +0 -20
  305. package/dist/analyzers/dryAnalyzerCompat.d.ts.map +0 -1
  306. package/dist/analyzers/dryAnalyzerCompat.js +0 -44
  307. package/dist/analyzers/dryAnalyzerCompat.js.map +0 -1
  308. package/dist/analyzers/schemaAnalyzerCompat.d.ts +0 -20
  309. package/dist/analyzers/schemaAnalyzerCompat.d.ts.map +0 -1
  310. package/dist/analyzers/schemaAnalyzerCompat.js +0 -69
  311. package/dist/analyzers/schemaAnalyzerCompat.js.map +0 -1
  312. package/dist/analyzers/solidAnalyzerCompat.d.ts +0 -20
  313. package/dist/analyzers/solidAnalyzerCompat.d.ts.map +0 -1
  314. package/dist/analyzers/solidAnalyzerCompat.js +0 -78
  315. package/dist/analyzers/solidAnalyzerCompat.js.map +0 -1
  316. package/dist/codeIndexDb.d.ts +0 -394
  317. package/dist/codeIndexDb.d.ts.map +0 -1
  318. package/dist/codeIndexDb.js +0 -2077
  319. package/dist/codeIndexDb.js.map +0 -1
  320. package/dist/languages/typescript/TypeScriptAdapter.d.ts +0 -108
  321. package/dist/languages/typescript/TypeScriptAdapter.d.ts.map +0 -1
  322. package/dist/languages/typescript/TypeScriptAdapter.js +0 -567
  323. package/dist/languages/typescript/TypeScriptAdapter.js.map +0 -1
  324. package/dist/languages/typescript/test-extraction.d.ts +0 -2
  325. package/dist/languages/typescript/test-extraction.d.ts.map +0 -1
  326. package/dist/languages/typescript/test-extraction.js +0 -36
  327. package/dist/languages/typescript/test-extraction.js.map +0 -1
  328. package/dist/mcp-hybrid.d.ts +0 -10
  329. package/dist/mcp-hybrid.d.ts.map +0 -1
  330. package/dist/mcp-hybrid.js +0 -41
  331. package/dist/mcp-hybrid.js.map +0 -1
  332. package/dist/mcp-index.d.ts +0 -13
  333. package/dist/mcp-index.d.ts.map +0 -1
  334. package/dist/mcp-index.js +0 -123
  335. package/dist/mcp-index.js.map +0 -1
  336. package/dist/mcp-standalone.d.ts +0 -3
  337. package/dist/mcp-standalone.d.ts.map +0 -1
  338. package/dist/mcp-standalone.js +0 -1201
  339. package/dist/mcp-standalone.js.map +0 -1
  340. package/dist/mcp-ui-server.d.ts +0 -27
  341. package/dist/mcp-ui-server.d.ts.map +0 -1
  342. package/dist/mcp-ui-server.js +0 -484
  343. package/dist/mcp-ui-server.js.map +0 -1
  344. package/dist/test-multi-strategy-search.d.ts +0 -3
  345. package/dist/test-multi-strategy-search.d.ts.map +0 -1
  346. package/dist/test-multi-strategy-search.js +0 -170
  347. package/dist/test-multi-strategy-search.js.map +0 -1
@@ -1,2077 +0,0 @@
1
- /**
2
- * Code Index Database using LokiJS + FlexSearch
3
- * Pure JavaScript implementation with no native dependencies
4
- */
5
- import Loki from 'lokijs';
6
- import * as FlexSearch from 'flexsearch';
7
- import { promises as fs } from 'fs';
8
- import path from 'path';
9
- import { ProjectTaskRepository } from './services/ProjectTaskRepository.js';
10
- import { QueryParser } from './search/QueryParser.js';
11
- import { getPersistedStorageRoot, resolvePersistedIndexPath } from './dataPaths.js';
12
- import { ContextualError, getErrnoCode } from './mcpToolErrors.js';
13
- import { WhitelistType, WhitelistStatus } from './types/whitelist.js';
14
- // Using SearchOptions and SearchResult from types.ts
15
- export class CodeIndexDB {
16
- static instance;
17
- /** Subclasses (e.g. EnhancedCodeIndexDB) need access for extra collections without `as any`. */
18
- db;
19
- functionsCollection = null;
20
- whitelistCollection = null;
21
- auditResultsCollection = null;
22
- analyzerConfigCollection = null;
23
- codeMapCollection = null;
24
- schemaCollection = null;
25
- schemaUsageCollection = null;
26
- /** User tasks: NOT cleared by clearIndex() — see clearIndex() */
27
- tasksCollection = null;
28
- taskRepository = null;
29
- searchIndex; // FlexSearch Document instance
30
- dbPath;
31
- isInitialized = false;
32
- constructor(dbPath = ':memory:') {
33
- this.dbPath = dbPath;
34
- this.db = new Loki(dbPath, {
35
- autosave: false,
36
- autosaveInterval: 0,
37
- autoload: false
38
- });
39
- // Initialize FlexSearch with full-text search configuration
40
- const { Document } = FlexSearch;
41
- this.searchIndex = new Document({
42
- document: {
43
- id: '$loki',
44
- index: [
45
- {
46
- field: 'name',
47
- tokenize: 'full',
48
- optimize: true,
49
- resolution: 9,
50
- bidirectional: true,
51
- weight: 10 // Highest weight for function name matches
52
- },
53
- {
54
- field: 'tokenizedName',
55
- tokenize: 'full',
56
- optimize: true,
57
- resolution: 9,
58
- bidirectional: true,
59
- weight: 9 // High weight for tokenized name matches
60
- },
61
- {
62
- field: 'signature',
63
- tokenize: 'full',
64
- optimize: true,
65
- resolution: 9,
66
- weight: 8 // High weight for signature matches
67
- },
68
- {
69
- field: 'purpose',
70
- tokenize: 'full',
71
- optimize: true,
72
- resolution: 9,
73
- weight: 7 // Important for semantic search
74
- },
75
- {
76
- field: 'context',
77
- tokenize: 'full',
78
- optimize: true,
79
- resolution: 9,
80
- weight: 6 // Contextual information
81
- },
82
- {
83
- field: 'jsDoc.description',
84
- tokenize: 'full',
85
- optimize: true,
86
- resolution: 9,
87
- weight: 5 // Documentation matches
88
- },
89
- {
90
- field: 'parameters[].name',
91
- tokenize: 'full',
92
- optimize: true,
93
- resolution: 7,
94
- weight: 4 // Parameter name matches
95
- },
96
- {
97
- field: 'parameters[].description',
98
- tokenize: 'full',
99
- optimize: true,
100
- resolution: 7,
101
- weight: 3 // Parameter description matches
102
- },
103
- {
104
- field: 'returnType',
105
- tokenize: 'full',
106
- optimize: true,
107
- resolution: 7,
108
- weight: 2 // Return type matches
109
- },
110
- {
111
- field: 'body',
112
- tokenize: 'full',
113
- optimize: true,
114
- resolution: 9,
115
- weight: 1 // Body content matches (lower weight to prioritize metadata matches)
116
- }
117
- ]
118
- },
119
- tokenize: 'full', // Global full-text tokenization
120
- optimize: true,
121
- resolution: 9,
122
- cache: 100,
123
- context: {
124
- depth: 3, // Enable contextual scoring
125
- bidirectional: true, // Search in both directions
126
- resolution: 9
127
- },
128
- threshold: 7, // Lower threshold for fuzzy matching
129
- depth: 3 // Search depth for better fuzzy matching
130
- });
131
- }
132
- static getInstance(dbPath) {
133
- if (!CodeIndexDB.instance) {
134
- const resolved = dbPath !== undefined && dbPath !== ''
135
- ? path.resolve(dbPath)
136
- : resolvePersistedIndexPath();
137
- CodeIndexDB.instance = new CodeIndexDB(resolved);
138
- }
139
- return CodeIndexDB.instance;
140
- }
141
- async initialize() {
142
- if (this.isInitialized)
143
- return;
144
- const dir = path.dirname(this.dbPath);
145
- const storageRoot = getPersistedStorageRoot();
146
- try {
147
- let st;
148
- try {
149
- st = await fs.stat(dir);
150
- }
151
- catch (e) {
152
- const code = getErrnoCode(e);
153
- if (code && code !== 'ENOENT') {
154
- throw new ContextualError(`Cannot access code index storage directory (${code}): ${dir}`, {
155
- errnoCode: code,
156
- storageRoot,
157
- dbPath: this.dbPath,
158
- hint: 'Fix permissions or set CODE_AUDITOR_DATA_DIR / --data-dir to a writable directory.',
159
- }, e instanceof Error ? e : undefined);
160
- }
161
- }
162
- if (st && !st.isDirectory()) {
163
- throw new ContextualError(`Code index storage path exists but is not a directory: ${dir}`, {
164
- storageRoot,
165
- dbPath: this.dbPath,
166
- hint: 'Remove the conflicting file/path or choose a different CODE_AUDITOR_DATA_DIR / --data-dir.',
167
- });
168
- }
169
- await fs.mkdir(dir, { recursive: true });
170
- }
171
- catch (e) {
172
- if (e instanceof ContextualError)
173
- throw e;
174
- const code = getErrnoCode(e);
175
- throw new ContextualError(`Failed to prepare code index storage: ${e instanceof Error ? e.message : String(e)}`, {
176
- ...(code && { errnoCode: code }),
177
- storageRoot,
178
- dbPath: this.dbPath,
179
- hint: 'Ensure the storage directory is writable (default: <cwd>/.code-index when CODE_AUDITOR_DATA_DIR is unset).',
180
- }, e instanceof Error ? e : undefined);
181
- }
182
- try {
183
- await new Promise((resolve, reject) => {
184
- this.db.loadDatabase({}, (err) => {
185
- if (err)
186
- reject(err);
187
- else
188
- resolve();
189
- });
190
- });
191
- }
192
- catch (e) {
193
- const code = getErrnoCode(e);
194
- throw new ContextualError(`Failed to load code index database: ${e instanceof Error ? e.message : String(e)}`, {
195
- ...(code && { errnoCode: code }),
196
- storageRoot,
197
- dbPath: this.dbPath,
198
- hint: 'The index file may be corrupted, locked by another process, or on a read-only volume. Try a different CODE_AUDITOR_DATA_DIR or delete index.db if safe.',
199
- }, e instanceof Error ? e : undefined);
200
- }
201
- // Get or create functions collection
202
- this.functionsCollection = this.db.getCollection('functions') ||
203
- this.db.addCollection('functions', {
204
- indices: ['name', 'filePath', 'language']
205
- });
206
- // Get or create whitelist collection
207
- this.whitelistCollection = this.db.getCollection('whitelist') ||
208
- this.db.addCollection('whitelist', {
209
- indices: ['name', 'type', 'status']
210
- });
211
- // Get or create audit results collection
212
- this.auditResultsCollection = this.db.getCollection('auditResults') ||
213
- this.db.addCollection('auditResults', {
214
- indices: ['auditId', 'timestamp', 'projectPath']
215
- });
216
- // Get or create analyzer config collection
217
- this.analyzerConfigCollection = this.db.getCollection('analyzerConfigs') ||
218
- this.db.addCollection('analyzerConfigs', {
219
- indices: ['analyzerName', 'projectPath', 'isGlobal']
220
- });
221
- // Get or create code map collection
222
- this.codeMapCollection = this.db.getCollection('codeMaps') ||
223
- this.db.addCollection('codeMaps', {
224
- indices: ['mapId', 'sectionType', 'timestamp']
225
- });
226
- // Get or create schema collection
227
- this.schemaCollection = this.db.getCollection('schemas') ||
228
- this.db.addCollection('schemas', {
229
- indices: ['schemaId', 'schemaName', 'indexedAt']
230
- });
231
- // Get or create schema usage collection
232
- this.schemaUsageCollection = this.db.getCollection('schemaUsage') ||
233
- this.db.addCollection('schemaUsage', {
234
- indices: ['tableName', 'filePath', 'functionName', 'usageType']
235
- });
236
- this.tasksCollection = this.db.getCollection('projectTasks') ||
237
- this.db.addCollection('projectTasks', {
238
- indices: ['taskId', 'projectPath', 'status', 'source', 'sortOrder']
239
- });
240
- // Initialize default whitelists if empty
241
- if (this.whitelistCollection.count() === 0) {
242
- await this.initializeDefaultWhitelists();
243
- }
244
- // Rebuild search index from existing data
245
- const existingDocs = this.functionsCollection.find();
246
- for (const doc of existingDocs) {
247
- const normalizedData = this.normalizeFunctionData(doc);
248
- this.searchIndex.add(normalizedData);
249
- }
250
- this.isInitialized = true;
251
- }
252
- ensureInitialized() {
253
- if (!this.isInitialized || !this.functionsCollection || !this.tasksCollection) {
254
- throw new Error('Database not initialized. Call initialize() first.');
255
- }
256
- }
257
- /**
258
- * Normalize function metadata for indexing
259
- * Converts FunctionMetadata to have required fields for FlexSearch
260
- */
261
- normalizeFunctionData(func) {
262
- // If it's already enhanced, return as-is with $loki if present
263
- if ('signature' in func && 'parameters' in func) {
264
- const normalized = { ...func };
265
- // Ensure nested jsDoc structure exists
266
- if (!normalized.jsDoc) {
267
- normalized.jsDoc = { description: func.purpose || '' };
268
- }
269
- // Add tokenized name to improve searchability
270
- normalized.tokenizedName = this.tokenizeFunctionName(func.name);
271
- return normalized;
272
- }
273
- // Convert basic FunctionMetadata to have searchable fields
274
- const enhanced = {
275
- ...func,
276
- signature: func.name, // Use name as signature for now
277
- parameters: [], // Empty parameters array
278
- jsDoc: {
279
- description: func.purpose || ''
280
- },
281
- returnType: 'unknown',
282
- // Add tokenized name for better searching
283
- tokenizedName: this.tokenizeFunctionName(func.name),
284
- // Extract body from metadata if present
285
- body: func.metadata?.body
286
- };
287
- return enhanced;
288
- }
289
- /**
290
- * Tokenize function name for better search
291
- * Splits camelCase, PascalCase, and snake_case into individual words
292
- */
293
- tokenizeFunctionName(name) {
294
- // Remove class prefix if present (e.g., "UserService.authenticate" -> "authenticate")
295
- const functionName = name.includes('.') ? name.split('.').pop() : name;
296
- // Split camelCase and PascalCase
297
- const camelSplit = functionName
298
- .replace(/([a-z])([A-Z])/g, '$1 $2')
299
- .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2');
300
- // Split snake_case and kebab-case
301
- const allSplit = camelSplit
302
- .replace(/[_-]/g, ' ')
303
- .toLowerCase()
304
- .trim();
305
- return allSplit;
306
- }
307
- async registerFunction(func) {
308
- this.ensureInitialized();
309
- try {
310
- // Check if function already exists (use composite key: name + filePath + lineNumber)
311
- const existing = this.functionsCollection.findOne({
312
- name: func.name,
313
- filePath: func.filePath,
314
- lineNumber: func.lineNumber
315
- });
316
- if (existing) {
317
- // Update existing
318
- Object.assign(existing, func);
319
- this.functionsCollection.update(existing);
320
- const normalizedData = this.normalizeFunctionData(existing);
321
- this.searchIndex.update(normalizedData);
322
- }
323
- else {
324
- // Insert new
325
- const doc = this.functionsCollection.insert(func);
326
- const normalizedData = this.normalizeFunctionData(doc);
327
- this.searchIndex.add(normalizedData);
328
- }
329
- // Force save
330
- this.db.saveDatabase();
331
- }
332
- catch (error) {
333
- throw new Error(`Failed to register function: ${error instanceof Error ? error.message : 'Unknown error'}`);
334
- }
335
- }
336
- async registerFunctions(functions) {
337
- this.ensureInitialized();
338
- let registered = 0;
339
- let failed = 0;
340
- const errors = [];
341
- for (const func of functions) {
342
- try {
343
- await this.registerFunction(func);
344
- registered++;
345
- }
346
- catch (error) {
347
- failed++;
348
- errors.push({
349
- function: func.name || 'unknown',
350
- error: error instanceof Error ? error.message : 'Unknown error'
351
- });
352
- }
353
- }
354
- return {
355
- success: failed === 0,
356
- registered,
357
- failed,
358
- errors: errors.length > 0 ? errors : undefined
359
- };
360
- }
361
- /**
362
- * Synchronize file index - add new functions, update existing, remove deleted
363
- * @param filePath The file path to synchronize
364
- * @param currentFunctions The current functions found in the file
365
- * @returns Sync statistics
366
- */
367
- async syncFileIndex(filePath, currentFunctions) {
368
- this.ensureInitialized();
369
- const stats = {
370
- added: 0,
371
- updated: 0,
372
- removed: 0
373
- };
374
- try {
375
- // Get all currently indexed functions for this file
376
- const indexedFunctions = this.functionsCollection.find({ filePath });
377
- // Create a map of current functions by composite key for quick lookup
378
- const createKey = (f) => `${f.name}:${f.filePath}:${f.lineNumber}`;
379
- const currentFunctionMap = new Map(currentFunctions.map(f => [createKey(f), f]));
380
- // Update or add functions
381
- for (const func of currentFunctions) {
382
- const existing = indexedFunctions.find(f => f.name === func.name &&
383
- f.filePath === func.filePath &&
384
- f.lineNumber === func.lineNumber);
385
- if (existing) {
386
- // Update existing function
387
- Object.assign(existing, func);
388
- this.functionsCollection.update(existing);
389
- const normalizedData = this.normalizeFunctionData(existing);
390
- this.searchIndex.update(normalizedData);
391
- stats.updated++;
392
- }
393
- else {
394
- // Add new function
395
- const doc = this.functionsCollection.insert(func);
396
- const normalizedData = this.normalizeFunctionData(doc);
397
- this.searchIndex.add(normalizedData);
398
- stats.added++;
399
- }
400
- }
401
- // Remove functions that no longer exist in the file
402
- for (const indexed of indexedFunctions) {
403
- const indexedKey = createKey(indexed);
404
- if (!currentFunctionMap.has(indexedKey)) {
405
- // Function no longer exists, remove it
406
- this.functionsCollection.remove(indexed);
407
- this.searchIndex.remove(indexed.$loki);
408
- stats.removed++;
409
- }
410
- }
411
- // Update dependency graph for the affected functions
412
- await this.updateDependencyGraph(filePath);
413
- // Force save
414
- this.db.saveDatabase();
415
- }
416
- catch (error) {
417
- throw new Error(`Failed to sync file index: ${error instanceof Error ? error.message : 'Unknown error'}`);
418
- }
419
- return stats;
420
- }
421
- /**
422
- * Update dependency graph - build reverse mappings for calledBy relationships
423
- * @param filePath Optional file path to limit the update scope
424
- */
425
- async updateDependencyGraph(filePath) {
426
- this.ensureInitialized();
427
- try {
428
- // Get all functions (or just those in the specified file)
429
- const functions = filePath
430
- ? this.functionsCollection.find({ filePath })
431
- : this.functionsCollection.find();
432
- // Clear existing calledBy relationships for affected functions
433
- for (const func of functions) {
434
- if (func.metadata) {
435
- func.metadata.calledBy = [];
436
- }
437
- }
438
- // Build calledBy relationships by iterating through all functions
439
- const allFunctions = this.functionsCollection.find();
440
- for (const caller of allFunctions) {
441
- if (caller.metadata?.functionCalls) {
442
- for (const callee of caller.metadata.functionCalls) {
443
- // Find the called function
444
- const calledFunc = this.findFunctionByQualifiedName(callee);
445
- if (calledFunc && calledFunc.metadata) {
446
- if (!calledFunc.metadata.calledBy) {
447
- calledFunc.metadata.calledBy = [];
448
- }
449
- // Add caller to calledBy list if not already present
450
- const callerName = this.getQualifiedFunctionName(caller);
451
- if (!calledFunc.metadata.calledBy.includes(callerName)) {
452
- calledFunc.metadata.calledBy.push(callerName);
453
- this.functionsCollection.update(calledFunc);
454
- }
455
- }
456
- }
457
- }
458
- }
459
- }
460
- catch (error) {
461
- throw new Error(`Failed to update dependency graph: ${error instanceof Error ? error.message : 'Unknown error'}`);
462
- }
463
- }
464
- /**
465
- * Find a function by its qualified name (e.g., "filePath#functionName")
466
- */
467
- findFunctionByQualifiedName(qualifiedName) {
468
- // Handle different name formats
469
- if (qualifiedName.includes('#')) {
470
- const [filePath, functionName] = qualifiedName.split('#');
471
- return this.functionsCollection.findOne({
472
- filePath: { $regex: filePath },
473
- name: functionName
474
- });
475
- }
476
- else {
477
- // Simple function name - search across all files
478
- return this.functionsCollection.findOne({ name: qualifiedName });
479
- }
480
- }
481
- /**
482
- * Get qualified name for a function
483
- */
484
- getQualifiedFunctionName(func) {
485
- return `${func.filePath}#${func.name}`;
486
- }
487
- /**
488
- * Get transitive dependencies for a function (functions it calls, directly and indirectly)
489
- * @param functionName The function to analyze
490
- * @param maxDepth Maximum depth to traverse (default 10)
491
- * @returns Array of function names with their depth
492
- */
493
- async getTransitiveDependencies(functionName, maxDepth = 10) {
494
- this.ensureInitialized();
495
- const dependencies = [];
496
- const visited = new Set();
497
- const traverse = (funcName, depth) => {
498
- if (depth > maxDepth || visited.has(funcName))
499
- return;
500
- visited.add(funcName);
501
- const func = this.findFunctionByQualifiedName(funcName);
502
- if (!func?.metadata?.functionCalls)
503
- return;
504
- for (const callee of func.metadata.functionCalls) {
505
- if (!visited.has(callee)) {
506
- dependencies.push({ name: callee, depth });
507
- traverse(callee, depth + 1);
508
- }
509
- }
510
- };
511
- traverse(functionName, 1);
512
- return dependencies;
513
- }
514
- /**
515
- * Get transitive callers for a function (functions that call it, directly and indirectly)
516
- * @param functionName The function to analyze
517
- * @param maxDepth Maximum depth to traverse (default 10)
518
- * @returns Array of function names with their depth
519
- */
520
- async getTransitiveCallers(functionName, maxDepth = 10) {
521
- this.ensureInitialized();
522
- const callers = [];
523
- const visited = new Set();
524
- const traverse = (funcName, depth) => {
525
- if (depth > maxDepth || visited.has(funcName))
526
- return;
527
- visited.add(funcName);
528
- const func = this.findFunctionByQualifiedName(funcName);
529
- if (!func?.metadata?.calledBy)
530
- return;
531
- for (const caller of func.metadata.calledBy) {
532
- if (!visited.has(caller)) {
533
- callers.push({ name: caller, depth });
534
- traverse(caller, depth + 1);
535
- }
536
- }
537
- };
538
- traverse(functionName, 1);
539
- return callers;
540
- }
541
- /**
542
- * Detect circular dependencies in the codebase
543
- * @returns Array of circular dependency chains
544
- */
545
- async detectCircularDependencies() {
546
- this.ensureInitialized();
547
- const cycles = [];
548
- const allFunctions = this.functionsCollection.find();
549
- for (const func of allFunctions) {
550
- if (!func.metadata?.functionCalls)
551
- continue;
552
- const funcName = this.getQualifiedFunctionName(func);
553
- const visited = new Set();
554
- const path = [];
555
- const hasCycle = (current) => {
556
- if (path.includes(current)) {
557
- // Found a cycle - extract it
558
- const cycleStart = path.indexOf(current);
559
- const cycle = [...path.slice(cycleStart), current];
560
- // Check if we already have this cycle (in any rotation)
561
- const isNewCycle = !cycles.some(existing => existing.length === cycle.length &&
562
- existing.every(f => cycle.includes(f)));
563
- if (isNewCycle) {
564
- cycles.push(cycle);
565
- }
566
- return true;
567
- }
568
- if (visited.has(current))
569
- return false;
570
- visited.add(current);
571
- path.push(current);
572
- const currentFunc = this.findFunctionByQualifiedName(current);
573
- if (currentFunc?.metadata?.functionCalls) {
574
- for (const callee of currentFunc.metadata.functionCalls) {
575
- if (hasCycle(callee))
576
- return true;
577
- }
578
- }
579
- path.pop();
580
- return false;
581
- };
582
- hasCycle(funcName);
583
- }
584
- return cycles;
585
- }
586
- /**
587
- * Calculate the maximum dependency depth for each function
588
- * Updates the dependencyDepth field in metadata
589
- */
590
- async calculateDependencyDepths() {
591
- this.ensureInitialized();
592
- const allFunctions = this.functionsCollection.find();
593
- for (const func of allFunctions) {
594
- const funcName = this.getQualifiedFunctionName(func);
595
- const dependencies = await this.getTransitiveDependencies(funcName);
596
- const maxDepth = dependencies.length > 0
597
- ? Math.max(...dependencies.map(d => d.depth))
598
- : 0;
599
- if (!func.metadata) {
600
- func.metadata = {};
601
- }
602
- func.metadata.dependencyDepth = maxDepth;
603
- this.functionsCollection.update(func);
604
- }
605
- this.db.saveDatabase();
606
- }
607
- /**
608
- * Get all functions from the index
609
- */
610
- async getAllFunctions() {
611
- this.ensureInitialized();
612
- const allFunctions = this.functionsCollection.find();
613
- // Return the full enhanced metadata
614
- return allFunctions.map(func => ({
615
- // Base metadata
616
- name: func.name,
617
- filePath: func.filePath,
618
- lineNumber: func.lineNumber,
619
- startLine: func.startLine,
620
- endLine: func.endLine,
621
- language: func.language,
622
- dependencies: func.dependencies,
623
- purpose: func.purpose,
624
- context: func.context,
625
- metadata: func.metadata,
626
- // Enhanced metadata
627
- signature: func.signature || '',
628
- parameters: func.parameters || [],
629
- jsDoc: typeof func.jsDoc === 'string' ? { description: func.jsDoc } : (func.jsDoc || {}),
630
- imports: func.imports || [],
631
- body: func.body || '',
632
- comments: func.comments || [],
633
- isAsync: func.isAsync || false,
634
- isGenerator: func.isGenerator || false,
635
- returnType: func.returnType,
636
- visibility: func.visibility || 'public',
637
- // Additional fields from metadata if available
638
- ...(func.metadata || {})
639
- }));
640
- }
641
- async searchFunctions(options) {
642
- this.ensureInitialized();
643
- const startTime = Date.now();
644
- let results = [];
645
- let searchScores = new Map(); // Map of $loki id to relevance score
646
- // Parse the query if provided
647
- const queryParser = new QueryParser();
648
- let parsedQuery;
649
- // Determine search mode
650
- const searchMode = options.searchMode || 'metadata';
651
- if (options.query) {
652
- parsedQuery = options.parsedQuery || queryParser.parse(options.query);
653
- // Check if there are search terms or just filters
654
- if (parsedQuery.terms.length > 0 || parsedQuery.phrases.length > 0) {
655
- if (searchMode === 'content') {
656
- // Content search only
657
- results = await this.executeContentSearch(parsedQuery, searchScores);
658
- }
659
- else if (searchMode === 'both') {
660
- // Both metadata and content search
661
- const metadataResults = await this.executeMultiStrategySearch(parsedQuery, searchScores);
662
- const contentResults = await this.executeContentSearch(parsedQuery, searchScores);
663
- // Merge results, combining scores for duplicates
664
- const resultMap = new Map();
665
- metadataResults.forEach(doc => {
666
- if (doc.$loki !== undefined) {
667
- resultMap.set(doc.$loki, doc);
668
- }
669
- });
670
- contentResults.forEach(doc => {
671
- if (doc.$loki !== undefined) {
672
- resultMap.set(doc.$loki, doc);
673
- // Combine scores
674
- const existingScore = searchScores.get(doc.$loki) || 0;
675
- searchScores.set(doc.$loki, existingScore);
676
- }
677
- });
678
- results = Array.from(resultMap.values());
679
- }
680
- else {
681
- // Metadata search only (default)
682
- results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
683
- }
684
- }
685
- else {
686
- // No search terms, just filters - get all
687
- results = this.functionsCollection.find();
688
- results.forEach((doc, index) => {
689
- if (doc.$loki !== undefined) {
690
- searchScores.set(doc.$loki, 50); // Base score for browse mode
691
- }
692
- });
693
- }
694
- }
695
- else if (options.parsedQuery) {
696
- parsedQuery = options.parsedQuery;
697
- // Check if there are search terms or just filters
698
- if (parsedQuery.terms.length > 0 || parsedQuery.phrases.length > 0) {
699
- if (searchMode === 'content') {
700
- results = await this.executeContentSearch(parsedQuery, searchScores);
701
- }
702
- else if (searchMode === 'both') {
703
- const metadataResults = await this.executeMultiStrategySearch(parsedQuery, searchScores);
704
- const contentResults = await this.executeContentSearch(parsedQuery, searchScores);
705
- // Merge results
706
- const resultMap = new Map();
707
- metadataResults.forEach(doc => {
708
- if (doc.$loki !== undefined) {
709
- resultMap.set(doc.$loki, doc);
710
- }
711
- });
712
- contentResults.forEach(doc => {
713
- if (doc.$loki !== undefined) {
714
- resultMap.set(doc.$loki, doc);
715
- // Combine scores
716
- const existingScore = searchScores.get(doc.$loki) || 0;
717
- searchScores.set(doc.$loki, existingScore);
718
- }
719
- });
720
- results = Array.from(resultMap.values());
721
- }
722
- else {
723
- results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
724
- }
725
- }
726
- else {
727
- // No search terms, just filters - get all
728
- results = this.functionsCollection.find();
729
- results.forEach((doc, index) => {
730
- if (doc.$loki !== undefined) {
731
- searchScores.set(doc.$loki, 50); // Base score for browse mode
732
- }
733
- });
734
- }
735
- }
736
- else {
737
- // No query, get all
738
- results = this.functionsCollection.find();
739
- // Give all results a base score
740
- results.forEach((doc, index) => {
741
- if (doc.$loki !== undefined) {
742
- searchScores.set(doc.$loki, 50); // Base score for browse mode
743
- }
744
- });
745
- }
746
- // Apply filters from both options and parsedQuery
747
- const combinedFilters = this.mergeFilters(options.filters, parsedQuery?.filters);
748
- results = this.applyFilters(results, combinedFilters);
749
- // Sort by relevance score
750
- results.sort((a, b) => {
751
- const scoreA = searchScores.get(a.$loki) || 0;
752
- const scoreB = searchScores.get(b.$loki) || 0;
753
- return scoreB - scoreA; // Higher scores first
754
- });
755
- // Apply pagination
756
- const totalCount = results.length;
757
- const limit = options.limit || 50;
758
- const offset = options.offset || 0;
759
- results = results.slice(offset, offset + limit);
760
- // Clean up results and add scores
761
- const functions = results.map((doc) => {
762
- const { $loki, meta, ...functionData } = doc;
763
- const score = searchScores.get($loki) || 0;
764
- return {
765
- ...functionData,
766
- score
767
- };
768
- });
769
- return {
770
- functions,
771
- totalCount,
772
- query: options.query,
773
- parsedQuery,
774
- executionTime: Date.now() - startTime
775
- };
776
- }
777
- /**
778
- * Execute multi-strategy search with different approaches
779
- * @param parsedQuery The parsed query object
780
- * @param searchScores Map to store relevance scores
781
- * @returns Array of matching documents
782
- */
783
- async executeMultiStrategySearch(parsedQuery, searchScores) {
784
- const resultsMap = new Map(); // Map of $loki id to document
785
- // Strategy 1: Exact phrase matching (highest weight)
786
- if (parsedQuery.phrases.length > 0) {
787
- for (const phrase of parsedQuery.phrases) {
788
- const phraseResults = await this.searchWithFlexSearch(phrase, 1000);
789
- this.mergeSearchResults(phraseResults, resultsMap, searchScores, 100); // High weight for exact phrases
790
- }
791
- }
792
- // Strategy 2: All terms must match (AND logic) - use original terms
793
- const originalTerms = parsedQuery.originalTerms || parsedQuery.terms;
794
- if (originalTerms.length > 0) {
795
- // For each original term, search including its synonyms
796
- const termResultSets = new Map();
797
- for (const originalTerm of originalTerms) {
798
- // Get synonyms for this term
799
- const queryParser = new QueryParser();
800
- const synonyms = queryParser.getSynonyms(originalTerm);
801
- const searchTerms = [originalTerm, ...synonyms];
802
- // Find documents containing ANY of the synonyms
803
- const termIds = new Set();
804
- for (const searchTerm of searchTerms) {
805
- const termResults = await this.searchWithFlexSearch(searchTerm, 1000);
806
- termResults.forEach(doc => {
807
- if (doc.$loki !== undefined)
808
- termIds.add(doc.$loki);
809
- });
810
- }
811
- termResultSets.set(originalTerm, termIds);
812
- }
813
- // Find documents that contain ALL original terms (or their synonyms)
814
- if (termResultSets.size > 0) {
815
- const allTermIds = this.intersectSets(Array.from(termResultSets.values()));
816
- if (allTermIds.size > 0) {
817
- const allTermDocs = this.functionsCollection.find({
818
- $loki: { $in: Array.from(allTermIds) }
819
- });
820
- // Calculate score based on how many fields matched
821
- allTermDocs.forEach(doc => {
822
- const baseScore = 80; // Base score for AND matches
823
- const fieldMatchBonus = this.calculateFieldMatchScore(doc, originalTerms);
824
- this.mergeSearchResults([doc], resultsMap, searchScores, baseScore + fieldMatchBonus);
825
- });
826
- }
827
- }
828
- }
829
- // Strategy 3: Any term can match (OR logic) - lower weight
830
- if (parsedQuery.terms.length > 0) {
831
- const orQuery = parsedQuery.terms.join(' ');
832
- const orResults = await this.searchWithFlexSearch(orQuery, 1000);
833
- // Score based on how many terms matched
834
- orResults.forEach(doc => {
835
- const matchCount = this.countMatchingTerms(doc, parsedQuery.terms);
836
- const score = 40 + (matchCount * 10); // Base score + bonus per matched term
837
- this.mergeSearchResults([doc], resultsMap, searchScores, score);
838
- });
839
- }
840
- // Strategy 4: Fuzzy search if enabled
841
- if (parsedQuery.fuzzy && parsedQuery.terms.length > 0) {
842
- // FlexSearch already provides fuzzy matching with threshold setting
843
- const fuzzyQuery = parsedQuery.terms.join(' ');
844
- const fuzzyResults = await this.searchWithFlexSearch(fuzzyQuery, 1000, true);
845
- this.mergeSearchResults(fuzzyResults, resultsMap, searchScores, 30); // Lower weight for fuzzy
846
- }
847
- // Apply excluded terms filter
848
- let finalResults = Array.from(resultsMap.values());
849
- if (parsedQuery.excludedTerms.length > 0) {
850
- finalResults = this.excludeTermsFromResults(finalResults, parsedQuery.excludedTerms);
851
- }
852
- return finalResults;
853
- }
854
- /**
855
- * Search using FlexSearch and return document results
856
- */
857
- async searchWithFlexSearch(query, limit, fuzzy = false) {
858
- const searchOptions = { limit };
859
- const searchResults = await this.searchIndex.search(query, searchOptions);
860
- // Extract loki IDs from FlexSearch results
861
- const lokiIds = [];
862
- for (const fieldResult of searchResults) {
863
- if (fieldResult.result && Array.isArray(fieldResult.result)) {
864
- lokiIds.push(...fieldResult.result);
865
- }
866
- }
867
- // Remove duplicates and fetch documents
868
- const uniqueLokiIds = [...new Set(lokiIds)];
869
- if (uniqueLokiIds.length === 0) {
870
- return [];
871
- }
872
- return this.functionsCollection.find({
873
- $loki: { $in: uniqueLokiIds }
874
- });
875
- }
876
- /**
877
- * Merge search results with scoring
878
- */
879
- mergeSearchResults(newResults, resultsMap, searchScores, weight) {
880
- newResults.forEach(doc => {
881
- if (doc.$loki === undefined)
882
- return;
883
- // Add or update document in results map
884
- resultsMap.set(doc.$loki, doc);
885
- // Update score (accumulate if already exists)
886
- const currentScore = searchScores.get(doc.$loki) || 0;
887
- searchScores.set(doc.$loki, currentScore + weight);
888
- });
889
- }
890
- /**
891
- * Calculate bonus score based on which fields matched
892
- */
893
- calculateFieldMatchScore(doc, terms) {
894
- let score = 0;
895
- const lowerTerms = terms.map(t => t.toLowerCase());
896
- // Check each field with different weights
897
- if (doc.name && lowerTerms.some(t => doc.name.toLowerCase().includes(t))) {
898
- score += 15; // High bonus for name match
899
- }
900
- if (doc.signature && lowerTerms.some(t => doc.signature.toLowerCase().includes(t))) {
901
- score += 10;
902
- }
903
- if (doc.purpose && lowerTerms.some(t => doc.purpose.toLowerCase().includes(t))) {
904
- score += 8;
905
- }
906
- if (doc.jsDoc?.description && lowerTerms.some(t => doc.jsDoc.description.toLowerCase().includes(t))) {
907
- score += 5;
908
- }
909
- return score;
910
- }
911
- /**
912
- * Count how many terms match in a document
913
- */
914
- countMatchingTerms(doc, terms) {
915
- let count = 0;
916
- const documentText = this.getDocumentSearchText(doc).toLowerCase();
917
- terms.forEach(term => {
918
- if (documentText.includes(term.toLowerCase())) {
919
- count++;
920
- }
921
- });
922
- return count;
923
- }
924
- /**
925
- * Get searchable text from document
926
- */
927
- getDocumentSearchText(doc) {
928
- const parts = [
929
- doc.name,
930
- doc.signature,
931
- doc.purpose,
932
- doc.context,
933
- doc.jsDoc?.description,
934
- doc.returnType,
935
- ...(doc.parameters || []).map(p => `${p.name} ${p.description || ''}`),
936
- ...doc.dependencies
937
- ];
938
- return parts.filter(Boolean).join(' ');
939
- }
940
- /**
941
- * Get synonyms for a term using QueryParser
942
- */
943
- getSynonyms(term) {
944
- const queryParser = new QueryParser();
945
- return queryParser.getSynonyms(term);
946
- }
947
- /**
948
- * Exclude documents containing any of the excluded terms
949
- */
950
- excludeTermsFromResults(results, excludedTerms) {
951
- return results.filter(doc => {
952
- const searchText = this.getDocumentSearchText(doc).toLowerCase();
953
- return !excludedTerms.some(term => searchText.includes(term.toLowerCase()));
954
- });
955
- }
956
- /**
957
- * Intersect multiple sets to find common elements
958
- */
959
- intersectSets(sets) {
960
- if (sets.length === 0)
961
- return new Set();
962
- if (sets.length === 1)
963
- return sets[0];
964
- const result = new Set(sets[0]);
965
- for (let i = 1; i < sets.length; i++) {
966
- for (const item of result) {
967
- if (!sets[i].has(item)) {
968
- result.delete(item);
969
- }
970
- }
971
- }
972
- return result;
973
- }
974
- /**
975
- * Merge filters from options and parsed query
976
- */
977
- mergeFilters(optionsFilters, queryFilters) {
978
- const merged = {};
979
- // Copy options filters
980
- if (optionsFilters) {
981
- Object.assign(merged, optionsFilters);
982
- }
983
- // Merge query filters (query filters take precedence)
984
- if (queryFilters) {
985
- if (queryFilters.language)
986
- merged.language = queryFilters.language;
987
- if (queryFilters.filePath)
988
- merged.filePath = queryFilters.filePath;
989
- if (queryFilters.fileType)
990
- merged.fileType = queryFilters.fileType;
991
- if (queryFilters.hasJsDoc !== undefined)
992
- merged.hasJsDoc = queryFilters.hasJsDoc;
993
- if (queryFilters.complexity)
994
- merged.complexity = queryFilters.complexity;
995
- if (queryFilters.dateRange)
996
- merged.dateRange = queryFilters.dateRange;
997
- if (queryFilters.metadata)
998
- merged.metadata = queryFilters.metadata;
999
- }
1000
- return merged;
1001
- }
1002
- /**
1003
- * Apply filters to search results
1004
- */
1005
- applyFilters(results, filters) {
1006
- if (!filters)
1007
- return results;
1008
- let filtered = results;
1009
- if (filters.language) {
1010
- filtered = filtered.filter(doc => doc.language === filters.language);
1011
- }
1012
- if (filters.filePath) {
1013
- // Support both exact match and includes based on the filter format
1014
- if (filters.filePath.includes('*') || filters.filePath.includes('?')) {
1015
- // Glob pattern - convert to regex
1016
- const pattern = filters.filePath
1017
- .replace(/\*/g, '.*')
1018
- .replace(/\?/g, '.')
1019
- .replace(/\//g, '\\/');
1020
- const regex = new RegExp(pattern);
1021
- filtered = filtered.filter(doc => regex.test(doc.filePath));
1022
- }
1023
- else if (filters.filePath.endsWith('.ts') || filters.filePath.endsWith('.tsx') ||
1024
- filters.filePath.endsWith('.js') || filters.filePath.endsWith('.jsx')) {
1025
- // If it looks like a full filename, match the end of the path
1026
- filtered = filtered.filter(doc => doc.filePath.endsWith(filters.filePath));
1027
- }
1028
- else {
1029
- // Otherwise, do substring match (for directory paths)
1030
- filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
1031
- }
1032
- }
1033
- if (filters.fileType) {
1034
- filtered = filtered.filter(doc => doc.filePath.endsWith(filters.fileType));
1035
- }
1036
- if (filters.hasJsDoc !== undefined) {
1037
- filtered = filtered.filter(doc => {
1038
- const hasJsDoc = doc.jsDoc && doc.jsDoc.description && doc.jsDoc.description.length > 0;
1039
- return filters.hasJsDoc ? hasJsDoc : !hasJsDoc;
1040
- });
1041
- }
1042
- if (filters.complexity) {
1043
- filtered = filtered.filter(doc => {
1044
- if (!doc.complexity)
1045
- return false;
1046
- const complexity = doc.complexity;
1047
- const min = filters.complexity.min || 0;
1048
- const max = filters.complexity.max || Infinity;
1049
- return complexity >= min && complexity <= max;
1050
- });
1051
- }
1052
- if (filters.hasAnyDependency && filters.hasAnyDependency.length > 0) {
1053
- filtered = filtered.filter(doc => filters.hasAnyDependency.some(dep => doc.dependencies.includes(dep)));
1054
- }
1055
- // Apply metadata filters
1056
- if (filters.metadata) {
1057
- filtered = filtered.filter(doc => {
1058
- if (!doc.metadata)
1059
- return false;
1060
- // Check entityType
1061
- if (filters.metadata.entityType &&
1062
- doc.metadata.entityType !== filters.metadata.entityType) {
1063
- return false;
1064
- }
1065
- // Check componentType
1066
- if (filters.metadata.componentType &&
1067
- doc.metadata.componentType !== filters.metadata.componentType) {
1068
- return false;
1069
- }
1070
- // Check hasHook
1071
- if (filters.metadata.hasHook && doc.metadata.hooks) {
1072
- const hasHook = doc.metadata.hooks.some(hook => hook.name.toLowerCase().includes(filters.metadata.hasHook.toLowerCase()));
1073
- if (!hasHook)
1074
- return false;
1075
- }
1076
- else if (filters.metadata.hasHook) {
1077
- return false; // No hooks but filter requires one
1078
- }
1079
- // Check hasProp
1080
- if (filters.metadata.hasProp && doc.metadata.props) {
1081
- const hasProp = doc.metadata.props.some(prop => prop.name.toLowerCase().includes(filters.metadata.hasProp.toLowerCase()));
1082
- if (!hasProp)
1083
- return false;
1084
- }
1085
- else if (filters.metadata.hasProp) {
1086
- return false; // No props but filter requires one
1087
- }
1088
- // Check usesDependency
1089
- if (filters.metadata.usesDependency) {
1090
- const dep = filters.metadata.usesDependency.toLowerCase();
1091
- // Check in both file-level dependencies and function-specific usedImports
1092
- const usesDepInFile = doc.dependencies.some(d => d.toLowerCase().includes(dep));
1093
- const usesDepInFunction = doc.metadata.usedImports?.some(imp => imp.toLowerCase().includes(dep)) || false;
1094
- if (!usesDepInFile && !usesDepInFunction)
1095
- return false;
1096
- }
1097
- // Check callsFunction
1098
- if (filters.metadata.callsFunction && doc.metadata.functionCalls) {
1099
- const targetFunc = filters.metadata.callsFunction.toLowerCase();
1100
- const callsFunc = doc.metadata.functionCalls.some(call => call.toLowerCase().includes(targetFunc));
1101
- if (!callsFunc)
1102
- return false;
1103
- }
1104
- else if (filters.metadata.callsFunction) {
1105
- return false; // No function calls but filter requires one
1106
- }
1107
- // Check calledByFunction
1108
- if (filters.metadata.calledByFunction && doc.metadata.calledBy) {
1109
- const callerFunc = filters.metadata.calledByFunction.toLowerCase();
1110
- const isCalledBy = doc.metadata.calledBy.some(caller => caller.toLowerCase().includes(callerFunc));
1111
- if (!isCalledBy)
1112
- return false;
1113
- }
1114
- else if (filters.metadata.calledByFunction) {
1115
- return false; // Not called by any function but filter requires one
1116
- }
1117
- // Check dependsOnModule
1118
- if (filters.metadata.dependsOnModule) {
1119
- const module = filters.metadata.dependsOnModule.toLowerCase();
1120
- // Check if any import or function call references this module
1121
- const dependsOnFile = doc.filePath.toLowerCase().includes(module);
1122
- const dependsOnImport = doc.dependencies.some(dep => dep.toLowerCase().includes(module));
1123
- const dependsOnCall = doc.metadata.functionCalls?.some(call => call.toLowerCase().includes(module)) || false;
1124
- if (!dependsOnFile && !dependsOnImport && !dependsOnCall)
1125
- return false;
1126
- }
1127
- // Check hasUnusedImports
1128
- if (filters.metadata.hasUnusedImports) {
1129
- const hasUnused = doc.metadata.unusedImports &&
1130
- doc.metadata.unusedImports.length > 0;
1131
- if (!hasUnused)
1132
- return false;
1133
- }
1134
- return true;
1135
- });
1136
- }
1137
- return filtered;
1138
- }
1139
- async findDefinition(name, filePath) {
1140
- this.ensureInitialized();
1141
- const query = { name };
1142
- if (filePath) {
1143
- query.filePath = filePath;
1144
- }
1145
- const result = this.functionsCollection.findOne(query);
1146
- if (!result)
1147
- return null;
1148
- const { $loki, meta, ...functionData } = result;
1149
- return functionData;
1150
- }
1151
- async getStats() {
1152
- this.ensureInitialized();
1153
- const allFunctions = this.functionsCollection.find();
1154
- // Calculate stats
1155
- const languages = {};
1156
- const dependencies = {};
1157
- const files = new Set();
1158
- for (const func of allFunctions) {
1159
- // Language stats
1160
- if (func.language) {
1161
- languages[func.language] = (languages[func.language] || 0) + 1;
1162
- }
1163
- // Dependency stats
1164
- for (const dep of func.dependencies) {
1165
- dependencies[dep] = (dependencies[dep] || 0) + 1;
1166
- }
1167
- // File stats
1168
- files.add(func.filePath);
1169
- }
1170
- // Top dependencies
1171
- const topDependencies = Object.entries(dependencies)
1172
- .sort((a, b) => b[1] - a[1])
1173
- .slice(0, 10)
1174
- .map(([name, count]) => ({ name, count }));
1175
- return {
1176
- totalFunctions: allFunctions.length,
1177
- languages,
1178
- topDependencies,
1179
- filesIndexed: files.size,
1180
- lastUpdated: new Date()
1181
- };
1182
- }
1183
- /**
1184
- * Drops analysis-derived data so search, cached audits, code maps, and schema overlays
1185
- * cannot reference removed or stale code ("ghost" analysis).
1186
- *
1187
- * Clears: indexed functions, FlexSearch, auditResults, codeMaps, schemas, schemaUsage.
1188
- * Preserves: projectTasks, analyzerConfigs, whitelist (user rules, not mirrored code).
1189
- *
1190
- * When adding collections that store derived code intelligence, clear them here unless
1191
- * they are durable user preferences (same category as analyzerConfigs).
1192
- */
1193
- async clearIndex() {
1194
- this.ensureInitialized();
1195
- this.functionsCollection.clear();
1196
- this.searchIndex.clear();
1197
- this.auditResultsCollection.clear();
1198
- this.codeMapCollection.clear();
1199
- this.schemaCollection.clear();
1200
- this.schemaUsageCollection.clear();
1201
- this.db.saveDatabase();
1202
- }
1203
- async close() {
1204
- if (this.isInitialized) {
1205
- this.taskRepository = null;
1206
- this.db.close();
1207
- this.isInitialized = false;
1208
- }
1209
- }
1210
- getTaskRepository() {
1211
- this.ensureInitialized();
1212
- if (!this.taskRepository) {
1213
- this.taskRepository = new ProjectTaskRepository(() => this.tasksCollection, () => this.db.saveDatabase());
1214
- }
1215
- return this.taskRepository;
1216
- }
1217
- /**
1218
- * Synchronize a single file's functions with the index
1219
- * Adds new functions, updates existing ones, and removes deleted ones
1220
- */
1221
- async synchronizeFile(filePath) {
1222
- this.ensureInitialized();
1223
- // fs is already imported at the top of the file
1224
- try {
1225
- // Check if file exists
1226
- await fs.access(filePath);
1227
- }
1228
- catch {
1229
- // File doesn't exist, remove all its functions
1230
- const existingFuncs = this.functionsCollection.find({ filePath });
1231
- for (const func of existingFuncs) {
1232
- this.functionsCollection.remove(func);
1233
- if (func.$loki !== undefined) {
1234
- await this.searchIndex.remove(func.$loki);
1235
- }
1236
- }
1237
- this.db.saveDatabase();
1238
- return { added: 0, updated: 0, removed: existingFuncs.length };
1239
- }
1240
- // Parse the file for functions
1241
- const { FunctionScanner } = await import('./functionScanner.js');
1242
- const scanner = new FunctionScanner();
1243
- const fileContent = await fs.readFile(filePath, 'utf-8');
1244
- const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'javascript';
1245
- const parsedFunctions = await scanner.scanFunctions(fileContent, filePath, language);
1246
- // Get existing functions
1247
- const existingFuncs = this.functionsCollection.find({ filePath });
1248
- const existingMap = new Map(existingFuncs.map(f => [f.name, f]));
1249
- const parsedMap = new Map(parsedFunctions.map(f => [f.name, f]));
1250
- let added = 0, updated = 0, removed = 0;
1251
- // Remove functions that no longer exist
1252
- for (const existing of existingFuncs) {
1253
- if (!parsedMap.has(existing.name)) {
1254
- this.functionsCollection.remove(existing);
1255
- if (existing.$loki !== undefined) {
1256
- await this.searchIndex.remove(existing.$loki);
1257
- }
1258
- removed++;
1259
- }
1260
- }
1261
- // Add or update functions
1262
- for (const parsed of parsedFunctions) {
1263
- const existing = existingMap.get(parsed.name);
1264
- if (existing) {
1265
- // Update existing
1266
- Object.assign(existing, parsed);
1267
- this.functionsCollection.update(existing);
1268
- const normalizedData = this.normalizeFunctionData(existing);
1269
- await this.searchIndex.update(normalizedData);
1270
- updated++;
1271
- }
1272
- else {
1273
- // Add new
1274
- await this.registerFunction(parsed);
1275
- added++;
1276
- }
1277
- }
1278
- if (added > 0 || updated > 0 || removed > 0) {
1279
- this.db.saveDatabase();
1280
- }
1281
- return { added, updated, removed };
1282
- }
1283
- /**
1284
- * Performs bulk cleanup of the index by verifying all indexed files still exist
1285
- * and removing entries for deleted files
1286
- */
1287
- async bulkCleanup() {
1288
- this.ensureInitialized();
1289
- // fs is already imported at the top of the file
1290
- const allFunctions = this.functionsCollection.find();
1291
- const fileMap = new Map();
1292
- // Group functions by file
1293
- for (const func of allFunctions) {
1294
- if (!fileMap.has(func.filePath)) {
1295
- fileMap.set(func.filePath, []);
1296
- }
1297
- fileMap.get(func.filePath).push(func);
1298
- }
1299
- const removedFiles = [];
1300
- const errors = [];
1301
- let removedCount = 0;
1302
- // Check each file
1303
- for (const [filePath, functions] of fileMap) {
1304
- try {
1305
- await fs.access(filePath);
1306
- // File exists, no action needed
1307
- }
1308
- catch (error) {
1309
- // File doesn't exist, remove all its functions
1310
- for (const func of functions) {
1311
- this.functionsCollection.remove(func);
1312
- // Remove from search index
1313
- if (func.$loki !== undefined) {
1314
- await this.searchIndex.remove(func.$loki);
1315
- }
1316
- removedCount++;
1317
- }
1318
- removedFiles.push(filePath);
1319
- }
1320
- }
1321
- // Save changes
1322
- if (removedCount > 0) {
1323
- this.db.saveDatabase();
1324
- }
1325
- return {
1326
- scannedCount: fileMap.size,
1327
- removedCount,
1328
- removedFiles,
1329
- errors
1330
- };
1331
- }
1332
- /**
1333
- * Performs deep synchronization by re-scanning all indexed files
1334
- * Updates function signatures and removes stale entries
1335
- */
1336
- async deepSync(progressCallback) {
1337
- this.ensureInitialized();
1338
- const allFunctions = this.functionsCollection.find();
1339
- const fileMap = new Map();
1340
- // Group functions by file
1341
- for (const func of allFunctions) {
1342
- if (!fileMap.has(func.filePath)) {
1343
- fileMap.set(func.filePath, []);
1344
- }
1345
- fileMap.get(func.filePath).push(func);
1346
- }
1347
- let syncedFiles = 0;
1348
- let totalAdded = 0;
1349
- let totalUpdated = 0;
1350
- let totalRemoved = 0;
1351
- const errors = [];
1352
- let current = 0;
1353
- const total = fileMap.size;
1354
- // Sync each file
1355
- for (const [filePath, _] of fileMap) {
1356
- current++;
1357
- if (progressCallback) {
1358
- progressCallback({ current, total, file: filePath });
1359
- }
1360
- try {
1361
- const result = await this.synchronizeFile(filePath);
1362
- if (result) {
1363
- syncedFiles++;
1364
- totalAdded += result.added;
1365
- totalUpdated += result.updated;
1366
- totalRemoved += result.removed;
1367
- }
1368
- }
1369
- catch (error) {
1370
- errors.push({
1371
- file: filePath,
1372
- error: error instanceof Error ? error.message : String(error)
1373
- });
1374
- }
1375
- }
1376
- return {
1377
- syncedFiles,
1378
- addedFunctions: totalAdded,
1379
- updatedFunctions: totalUpdated,
1380
- removedFunctions: totalRemoved,
1381
- errors
1382
- };
1383
- }
1384
- /**
1385
- * Execute content search - search within function bodies
1386
- */
1387
- async executeContentSearch(parsedQuery, searchScores) {
1388
- const resultsMap = new Map();
1389
- // Get all functions (we'll filter them)
1390
- const allFunctions = this.functionsCollection.find();
1391
- // Search in function bodies
1392
- for (const doc of allFunctions) {
1393
- // Check for body in multiple possible locations
1394
- const body = doc.body || doc.metadata?.body;
1395
- if (!body)
1396
- continue;
1397
- let score = 0;
1398
- const bodyLower = body.toLowerCase();
1399
- const matches = [];
1400
- // Split body into lines for line-level matching
1401
- const lines = body.split('\n');
1402
- // Check for exact phrases
1403
- for (const phrase of parsedQuery.phrases) {
1404
- const phraseLower = phrase.toLowerCase();
1405
- lines.forEach((line, lineIndex) => {
1406
- const lineLower = line.toLowerCase();
1407
- let columnIndex = lineLower.indexOf(phraseLower);
1408
- while (columnIndex !== -1) {
1409
- matches.push({
1410
- term: phrase,
1411
- line: (doc.lineNumber || 0) + lineIndex,
1412
- column: columnIndex + 1
1413
- });
1414
- score += 100; // High score for exact phrase match
1415
- columnIndex = lineLower.indexOf(phraseLower, columnIndex + 1);
1416
- }
1417
- });
1418
- }
1419
- // Check for individual terms
1420
- for (const term of parsedQuery.terms) {
1421
- const termLower = term.toLowerCase();
1422
- lines.forEach((line, lineIndex) => {
1423
- const lineLower = line.toLowerCase();
1424
- let columnIndex = lineLower.indexOf(termLower);
1425
- while (columnIndex !== -1) {
1426
- matches.push({
1427
- term: term,
1428
- line: (doc.lineNumber || 0) + lineIndex,
1429
- column: columnIndex + 1
1430
- });
1431
- score += 20; // Score for term match
1432
- columnIndex = lineLower.indexOf(termLower, columnIndex + 1);
1433
- }
1434
- });
1435
- }
1436
- // Check for excluded terms
1437
- let excluded = false;
1438
- for (const excludedTerm of parsedQuery.excludedTerms) {
1439
- if (bodyLower.includes(excludedTerm.toLowerCase())) {
1440
- excluded = true;
1441
- break;
1442
- }
1443
- }
1444
- // Add to results if score > 0 and not excluded
1445
- if (score > 0 && !excluded && doc.$loki !== undefined) {
1446
- // Store match information in metadata
1447
- const resultDoc = { ...doc };
1448
- if (!resultDoc.metadata)
1449
- resultDoc.metadata = {};
1450
- resultDoc.metadata.contentMatches = matches;
1451
- // Add match context (surrounding lines)
1452
- const contextLines = 2; // Number of lines before and after to include
1453
- const matchContexts = [];
1454
- // Group matches by line to avoid duplicate context
1455
- const matchesByLine = new Map();
1456
- for (const match of matches) {
1457
- const relativeLineNum = match.line - (doc.lineNumber || 0);
1458
- if (!matchesByLine.has(relativeLineNum)) {
1459
- matchesByLine.set(relativeLineNum, []);
1460
- }
1461
- matchesByLine.get(relativeLineNum).push(match);
1462
- }
1463
- // Build context for each unique line
1464
- for (const [relativeLineNum, lineMatches] of matchesByLine) {
1465
- if (relativeLineNum >= 0 && relativeLineNum < lines.length) {
1466
- const before = [];
1467
- const after = [];
1468
- // Get lines before
1469
- for (let i = Math.max(0, relativeLineNum - contextLines); i < relativeLineNum; i++) {
1470
- before.push(lines[i]);
1471
- }
1472
- // Get lines after
1473
- for (let i = relativeLineNum + 1; i < Math.min(lines.length, relativeLineNum + contextLines + 1); i++) {
1474
- after.push(lines[i]);
1475
- }
1476
- // Add context for each match on this line
1477
- for (const match of lineMatches) {
1478
- matchContexts.push({
1479
- match,
1480
- context: {
1481
- before,
1482
- line: lines[relativeLineNum],
1483
- after
1484
- }
1485
- });
1486
- }
1487
- }
1488
- }
1489
- resultDoc.metadata.matchContexts = matchContexts;
1490
- resultsMap.set(doc.$loki, resultDoc);
1491
- searchScores.set(doc.$loki, score);
1492
- }
1493
- }
1494
- return Array.from(resultsMap.values());
1495
- }
1496
- /**
1497
- * Initialize default whitelist entries
1498
- */
1499
- async initializeDefaultWhitelists() {
1500
- const defaultEntries = [
1501
- // JavaScript built-ins
1502
- { name: 'Date', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1503
- { name: 'Error', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1504
- { name: 'Array', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1505
- { name: 'Map', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1506
- { name: 'Set', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1507
- { name: 'Promise', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1508
- { name: 'RegExp', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1509
- // DOM/Browser APIs
1510
- { name: 'URL', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1511
- { name: 'URLSearchParams', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1512
- { name: 'FormData', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1513
- { name: 'Headers', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1514
- // Node.js built-ins
1515
- { name: 'fs', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['fs', 'node:fs', 'fs/promises'], addedBy: 'system', addedAt: new Date() },
1516
- { name: 'path', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['path', 'node:path'], addedBy: 'system', addedAt: new Date() },
1517
- { name: 'crypto', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['crypto', 'node:crypto'], addedBy: 'system', addedAt: new Date() },
1518
- // Next.js framework classes
1519
- { name: 'NextResponse', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
1520
- { name: 'NextRequest', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
1521
- { name: 'Response', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
1522
- { name: 'Request', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
1523
- // React core classes and functions
1524
- { name: 'Component', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1525
- { name: 'PureComponent', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1526
- { name: 'Fragment', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1527
- { name: 'StrictMode', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1528
- { name: 'Suspense', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1529
- // Database clients
1530
- { name: 'Pool', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
1531
- { name: 'Client', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
1532
- { name: 'MongoClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['mongodb'], addedBy: 'system', addedAt: new Date() },
1533
- { name: 'PrismaClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['@prisma/client'], addedBy: 'system', addedAt: new Date() },
1534
- // Auth libraries
1535
- { name: 'StackServerApp', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
1536
- { name: 'StackClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
1537
- { name: 'ClerkProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@clerk/nextjs'], addedBy: 'system', addedAt: new Date() },
1538
- { name: 'Auth0Provider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@auth0/nextjs-auth0'], addedBy: 'system', addedAt: new Date() },
1539
- // HTTP/API clients
1540
- { name: 'Axios', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', patterns: ['axios'], addedBy: 'system', addedAt: new Date() },
1541
- { name: 'HttpClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', addedBy: 'system', addedAt: new Date() },
1542
- // Express/Node.js frameworks
1543
- { name: 'Router', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
1544
- { name: 'Application', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
1545
- // Testing frameworks
1546
- { name: 'TestingModule', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@nestjs/testing'], addedBy: 'system', addedAt: new Date() },
1547
- { name: 'MockedProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@apollo/client/testing'], addedBy: 'system', addedAt: new Date() },
1548
- // Node.js core modules
1549
- { name: 'EventEmitter', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['events', 'node:events'], addedBy: 'system', addedAt: new Date() },
1550
- { name: 'Readable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
1551
- { name: 'Writable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
1552
- { name: 'Transform', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
1553
- { name: 'Buffer', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', addedBy: 'system', addedAt: new Date() },
1554
- ];
1555
- for (const entry of defaultEntries) {
1556
- this.whitelistCollection.insert(entry);
1557
- }
1558
- }
1559
- /**
1560
- * Get all whitelist entries
1561
- */
1562
- async getWhitelist(type, status) {
1563
- this.ensureInitialized();
1564
- const query = {};
1565
- if (type)
1566
- query.type = type;
1567
- if (status)
1568
- query.status = status;
1569
- return this.whitelistCollection.find(query);
1570
- }
1571
- /**
1572
- * Add a whitelist entry
1573
- */
1574
- async addWhitelistEntry(entry) {
1575
- this.ensureInitialized();
1576
- const newEntry = {
1577
- ...entry,
1578
- addedAt: new Date()
1579
- };
1580
- return this.whitelistCollection.insert(newEntry);
1581
- }
1582
- /**
1583
- * Update whitelist entry status
1584
- */
1585
- async updateWhitelistStatus(name, status) {
1586
- this.ensureInitialized();
1587
- const entry = this.whitelistCollection.findOne({ name });
1588
- if (entry) {
1589
- entry.status = status;
1590
- entry.updatedAt = new Date();
1591
- this.whitelistCollection.update(entry);
1592
- }
1593
- }
1594
- /**
1595
- * Check if a class/import is whitelisted
1596
- */
1597
- isWhitelisted(name, type) {
1598
- if (!this.isInitialized)
1599
- return false;
1600
- const entries = this.whitelistCollection.find({
1601
- type,
1602
- status: WhitelistStatus.Active
1603
- });
1604
- return entries.some(entry => {
1605
- // Direct name match
1606
- if (entry.name === name)
1607
- return true;
1608
- // Pattern match
1609
- if (entry.patterns) {
1610
- return entry.patterns.some(pattern => {
1611
- if (pattern.includes('*')) {
1612
- const regex = new RegExp(pattern.replace(/\*/g, '.*'));
1613
- return regex.test(name);
1614
- }
1615
- return pattern === name;
1616
- });
1617
- }
1618
- return false;
1619
- });
1620
- }
1621
- /**
1622
- * Detect and suggest new whitelist entries based on usage patterns
1623
- */
1624
- async detectWhitelistCandidates() {
1625
- this.ensureInitialized();
1626
- // This would analyze the codebase for patterns
1627
- // For now, returning empty array - to be implemented
1628
- return [];
1629
- }
1630
- /**
1631
- * Store audit results in database
1632
- */
1633
- async storeAuditResults(auditResult, projectPath) {
1634
- this.ensureInitialized();
1635
- // Generate unique audit ID
1636
- const auditId = `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1637
- // Store the entire result to preserve analyzer structure; auditId must come last so
1638
- // a stray auditResult.auditId cannot break pagination lookups.
1639
- const auditRecord = {
1640
- ...auditResult,
1641
- auditId,
1642
- timestamp: new Date(),
1643
- projectPath,
1644
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // Expire after 24 hours
1645
- };
1646
- this.auditResultsCollection.insert(auditRecord);
1647
- // Clean up expired results
1648
- this.cleanupExpiredAudits();
1649
- // Persist so resultId survives MCP restart (Loki autosave is off; many code paths never save otherwise).
1650
- this.db.saveDatabase();
1651
- return auditId;
1652
- }
1653
- /**
1654
- * Retrieve cached audit results
1655
- */
1656
- async getAuditResults(auditId) {
1657
- this.ensureInitialized();
1658
- const result = this.auditResultsCollection.findOne({ auditId });
1659
- if (result && new Date(result.expiresAt) > new Date()) {
1660
- return result;
1661
- }
1662
- // Remove expired result if found
1663
- if (result) {
1664
- this.auditResultsCollection.remove(result);
1665
- }
1666
- return null;
1667
- }
1668
- /**
1669
- * Clean up expired audit results
1670
- */
1671
- cleanupExpiredAudits() {
1672
- const now = new Date();
1673
- const expired = this.auditResultsCollection.find({
1674
- expiresAt: { $lt: now }
1675
- });
1676
- expired.forEach(doc => {
1677
- this.auditResultsCollection.remove(doc);
1678
- });
1679
- }
1680
- // Analyzer Configuration Methods
1681
- /**
1682
- * Store analyzer configuration
1683
- */
1684
- async storeAnalyzerConfig(analyzerName, config, options) {
1685
- this.ensureInitialized();
1686
- const configDoc = {
1687
- analyzerName,
1688
- config,
1689
- isGlobal: options?.isGlobal ?? true,
1690
- projectPath: options?.projectPath || null,
1691
- createdAt: new Date(),
1692
- updatedAt: new Date(),
1693
- createdBy: 'user',
1694
- metadata: options?.metadata
1695
- };
1696
- // Check if config already exists
1697
- const existing = this.analyzerConfigCollection.findOne({
1698
- analyzerName,
1699
- projectPath: options?.projectPath || null,
1700
- isGlobal: options?.isGlobal ?? true
1701
- });
1702
- if (existing) {
1703
- Object.assign(existing, {
1704
- config,
1705
- updatedAt: new Date(),
1706
- metadata: options?.metadata
1707
- });
1708
- this.analyzerConfigCollection.update(existing);
1709
- }
1710
- else {
1711
- this.analyzerConfigCollection.insert(configDoc);
1712
- }
1713
- this.db.saveDatabase();
1714
- return analyzerName;
1715
- }
1716
- /**
1717
- * Retrieve analyzer configuration
1718
- */
1719
- async getAnalyzerConfig(analyzerName, projectPath) {
1720
- this.ensureInitialized();
1721
- // First try project-specific config
1722
- if (projectPath) {
1723
- const projectConfig = this.analyzerConfigCollection.findOne({
1724
- analyzerName,
1725
- projectPath,
1726
- isGlobal: false
1727
- });
1728
- if (projectConfig)
1729
- return projectConfig.config;
1730
- }
1731
- // Fall back to global config
1732
- const globalConfig = this.analyzerConfigCollection.findOne({
1733
- analyzerName,
1734
- isGlobal: true
1735
- });
1736
- return globalConfig?.config || null;
1737
- }
1738
- /**
1739
- * Get all analyzer configurations
1740
- */
1741
- async getAllAnalyzerConfigs(projectPath) {
1742
- this.ensureInitialized();
1743
- const configs = {};
1744
- // Get global configs first
1745
- const globalConfigs = this.analyzerConfigCollection.find({ isGlobal: true });
1746
- for (const config of globalConfigs) {
1747
- configs[config.analyzerName] = config.config;
1748
- }
1749
- // Override with project-specific configs if available
1750
- if (projectPath) {
1751
- const projectConfigs = this.analyzerConfigCollection.find({
1752
- projectPath,
1753
- isGlobal: false
1754
- });
1755
- for (const config of projectConfigs) {
1756
- configs[config.analyzerName] = config.config;
1757
- }
1758
- }
1759
- return configs;
1760
- }
1761
- /**
1762
- * Delete analyzer configuration
1763
- */
1764
- async deleteAnalyzerConfig(analyzerName, options) {
1765
- this.ensureInitialized();
1766
- const config = this.analyzerConfigCollection.findOne({
1767
- analyzerName,
1768
- projectPath: options?.projectPath || null,
1769
- isGlobal: options?.isGlobal ?? !options?.projectPath
1770
- });
1771
- if (config) {
1772
- this.analyzerConfigCollection.remove(config);
1773
- this.db.saveDatabase();
1774
- return true;
1775
- }
1776
- return false;
1777
- }
1778
- /**
1779
- * Reset all analyzer configurations to defaults
1780
- */
1781
- async resetAnalyzerConfigs(projectPath) {
1782
- this.ensureInitialized();
1783
- if (projectPath) {
1784
- // Remove only project-specific configs
1785
- const projectConfigs = this.analyzerConfigCollection.find({
1786
- projectPath,
1787
- isGlobal: false
1788
- });
1789
- projectConfigs.forEach(config => {
1790
- this.analyzerConfigCollection.remove(config);
1791
- });
1792
- }
1793
- else {
1794
- // Remove all configs
1795
- this.analyzerConfigCollection.clear();
1796
- }
1797
- this.db.saveDatabase();
1798
- }
1799
- /**
1800
- * Store a code map section in the database
1801
- */
1802
- async storeCodeMapSection(mapId, sectionType, content, metadata) {
1803
- this.ensureInitialized();
1804
- // Remove any existing section of this type for this mapId
1805
- this.codeMapCollection.findAndRemove({
1806
- mapId,
1807
- sectionType
1808
- });
1809
- this.codeMapCollection.insert({
1810
- mapId,
1811
- sectionType,
1812
- content,
1813
- metadata: metadata || {},
1814
- timestamp: new Date(),
1815
- size: content.length
1816
- });
1817
- this.db.saveDatabase();
1818
- }
1819
- /**
1820
- * Retrieve a code map section
1821
- */
1822
- async getCodeMapSection(mapId, sectionType) {
1823
- this.ensureInitialized();
1824
- const section = this.codeMapCollection.findOne({
1825
- mapId,
1826
- sectionType
1827
- });
1828
- return section ? {
1829
- content: section.content,
1830
- metadata: section.metadata || {}
1831
- } : null;
1832
- }
1833
- /**
1834
- * List available code map sections for a mapId
1835
- */
1836
- async listCodeMapSections(mapId) {
1837
- this.ensureInitialized();
1838
- const sections = this.codeMapCollection.find({ mapId });
1839
- return sections.map(section => ({
1840
- sectionType: section.sectionType,
1841
- size: section.size || 0,
1842
- timestamp: section.timestamp
1843
- }));
1844
- }
1845
- /**
1846
- * Clear code map sections older than specified time
1847
- */
1848
- async clearOldCodeMaps(olderThanHours = 24) {
1849
- this.ensureInitialized();
1850
- const cutoffTime = new Date();
1851
- cutoffTime.setHours(cutoffTime.getHours() - olderThanHours);
1852
- const oldSections = this.codeMapCollection.find({
1853
- timestamp: { '$lt': cutoffTime }
1854
- });
1855
- const count = oldSections.length;
1856
- this.codeMapCollection.findAndRemove({
1857
- timestamp: { '$lt': cutoffTime }
1858
- });
1859
- this.db.saveDatabase();
1860
- return count;
1861
- }
1862
- /**
1863
- * Delete all sections for a specific mapId
1864
- */
1865
- async deleteCodeMap(mapId) {
1866
- this.ensureInitialized();
1867
- const sections = this.codeMapCollection.find({ mapId });
1868
- const count = sections.length;
1869
- this.codeMapCollection.findAndRemove({ mapId });
1870
- this.db.saveDatabase();
1871
- return count;
1872
- }
1873
- // Schema Management Methods
1874
- /**
1875
- * Store a database schema definition
1876
- */
1877
- async storeSchema(schema) {
1878
- this.ensureInitialized();
1879
- const schemaId = `schema_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1880
- const schemaMetadata = {
1881
- schemaId,
1882
- schemaName: schema.name,
1883
- indexedAt: new Date(),
1884
- tableCount: schema.databases.reduce((acc, db) => acc + db.tables.length, 0),
1885
- relationshipCount: schema.databases.reduce((acc, db) => acc + (db.relationships?.length || 0), 0),
1886
- usagePatterns: [],
1887
- discoveredPatterns: [],
1888
- violations: []
1889
- };
1890
- // Store the schema definition
1891
- this.schemaCollection.insert({
1892
- schemaId,
1893
- schema,
1894
- metadata: schemaMetadata,
1895
- indexedAt: new Date()
1896
- });
1897
- this.db.saveDatabase();
1898
- return schemaId;
1899
- }
1900
- /**
1901
- * Retrieve a schema by ID
1902
- */
1903
- async getSchema(schemaId) {
1904
- this.ensureInitialized();
1905
- const result = this.schemaCollection.findOne({ schemaId });
1906
- return result?.schema || null;
1907
- }
1908
- /**
1909
- * Get all schemas
1910
- */
1911
- async getAllSchemas() {
1912
- this.ensureInitialized();
1913
- const schemas = this.schemaCollection.find();
1914
- return schemas.map(doc => ({
1915
- schemaId: doc.schemaId,
1916
- metadata: doc.metadata,
1917
- schema: doc.schema
1918
- }));
1919
- }
1920
- /**
1921
- * Delete a schema
1922
- */
1923
- async deleteSchema(schemaId) {
1924
- this.ensureInitialized();
1925
- const schema = this.schemaCollection.findOne({ schemaId });
1926
- if (schema) {
1927
- this.schemaCollection.remove(schema);
1928
- // Also remove associated usage patterns
1929
- this.schemaUsageCollection.findAndRemove({ schemaId });
1930
- this.db.saveDatabase();
1931
- return true;
1932
- }
1933
- return false;
1934
- }
1935
- /**
1936
- * Record schema usage in code
1937
- */
1938
- async recordSchemaUsage(usage, schemaId) {
1939
- this.ensureInitialized();
1940
- const usageDoc = {
1941
- ...usage,
1942
- schemaId: schemaId || 'default',
1943
- recordedAt: new Date()
1944
- };
1945
- // Check if this usage already exists
1946
- const existing = this.schemaUsageCollection.findOne({
1947
- tableName: usage.tableName,
1948
- filePath: usage.filePath,
1949
- functionName: usage.functionName,
1950
- line: usage.line
1951
- });
1952
- if (existing) {
1953
- Object.assign(existing, usageDoc);
1954
- this.schemaUsageCollection.update(existing);
1955
- }
1956
- else {
1957
- this.schemaUsageCollection.insert(usageDoc);
1958
- }
1959
- this.db.saveDatabase();
1960
- }
1961
- /**
1962
- * Get schema usage patterns
1963
- */
1964
- async getSchemaUsage(options = {}) {
1965
- this.ensureInitialized();
1966
- const query = {};
1967
- if (options.schemaId)
1968
- query.schemaId = options.schemaId;
1969
- if (options.tableName)
1970
- query.tableName = options.tableName;
1971
- if (options.filePath)
1972
- query.filePath = options.filePath;
1973
- if (options.functionName)
1974
- query.functionName = options.functionName;
1975
- if (options.usageType)
1976
- query.usageType = options.usageType;
1977
- const results = this.schemaUsageCollection.find(query);
1978
- return results.map(doc => {
1979
- const { $loki, meta, schemaId, recordedAt, ...usage } = doc;
1980
- return usage;
1981
- });
1982
- }
1983
- /**
1984
- * Find functions that interact with specific tables
1985
- */
1986
- async findFunctionsUsingTable(tableName) {
1987
- this.ensureInitialized();
1988
- const usages = this.schemaUsageCollection.find({ tableName });
1989
- return usages.map(usage => ({
1990
- functionName: usage.functionName,
1991
- filePath: usage.filePath,
1992
- usageType: usage.usageType,
1993
- line: usage.line
1994
- }));
1995
- }
1996
- /**
1997
- * Get schema statistics
1998
- */
1999
- async getSchemaStats() {
2000
- this.ensureInitialized();
2001
- const schemas = this.schemaCollection.find();
2002
- const usages = this.schemaUsageCollection.find();
2003
- const totalTables = schemas.reduce((acc, schema) => acc + schema.schema.databases.reduce((dbAcc, db) => dbAcc + db.tables.length, 0), 0);
2004
- // Count usage by table
2005
- const tableUsage = {};
2006
- const usageByType = {};
2007
- usages.forEach(usage => {
2008
- tableUsage[usage.tableName] = (tableUsage[usage.tableName] || 0) + 1;
2009
- usageByType[usage.usageType] = (usageByType[usage.usageType] || 0) + 1;
2010
- });
2011
- const mostUsedTables = Object.entries(tableUsage)
2012
- .sort((a, b) => b[1] - a[1])
2013
- .slice(0, 10)
2014
- .map(([tableName, usageCount]) => ({ tableName, usageCount }));
2015
- return {
2016
- totalSchemas: schemas.length,
2017
- totalTables,
2018
- totalUsagePatterns: usages.length,
2019
- mostUsedTables,
2020
- usageByType
2021
- };
2022
- }
2023
- /**
2024
- * Update search to include schema context
2025
- */
2026
- async searchWithSchemaContext(query, options = {}) {
2027
- this.ensureInitialized();
2028
- // First perform regular function search
2029
- const searchResult = await this.searchFunctions(options);
2030
- if (!options.includeSchemaUsage) {
2031
- return searchResult;
2032
- }
2033
- // Add schema context to results
2034
- const enhancedFunctions = await Promise.all(searchResult.functions.map(async (func) => {
2035
- const schemaUsage = await this.getSchemaUsage({
2036
- filePath: func.filePath,
2037
- functionName: func.name
2038
- });
2039
- return {
2040
- ...func,
2041
- schemaUsage,
2042
- affectedTables: [...new Set(schemaUsage.map(u => u.tableName))],
2043
- schemaPatterns: [...new Set(schemaUsage.map(u => u.usageType))]
2044
- };
2045
- }));
2046
- // Extract schema context for summary
2047
- const allSchemaUsage = enhancedFunctions.flatMap(f => f.schemaUsage || []);
2048
- const schemaContext = [...new Set(allSchemaUsage.map(u => ({
2049
- tableName: u.tableName,
2050
- usageType: u.usageType
2051
- })))];
2052
- return {
2053
- ...searchResult,
2054
- functions: enhancedFunctions,
2055
- schemaContext
2056
- };
2057
- }
2058
- async createProjectTask(input) {
2059
- return this.getTaskRepository().create(input);
2060
- }
2061
- async getProjectTask(taskId) {
2062
- return this.getTaskRepository().getById(taskId);
2063
- }
2064
- async listProjectTasks(projectPath, options) {
2065
- return this.getTaskRepository().list(projectPath, options);
2066
- }
2067
- /**
2068
- * @param patch Raw JSON-shaped update; unknown keys are ignored (see sanitizeUpdatePatch).
2069
- */
2070
- async updateProjectTask(taskId, patch) {
2071
- return this.getTaskRepository().update(taskId, patch);
2072
- }
2073
- async deleteProjectTask(taskId) {
2074
- return this.getTaskRepository().delete(taskId);
2075
- }
2076
- }
2077
- //# sourceMappingURL=codeIndexDB.js.map