code-auditor-mcp 2.6.2 → 3.0.1

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