code-auditor-mcp 2.6.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/README.md +319 -147
  2. package/dist/analyzers/analyzerUtils.d.ts +13 -11
  3. package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
  4. package/dist/analyzers/analyzerUtils.js +22 -56
  5. package/dist/analyzers/analyzerUtils.js.map +1 -1
  6. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  7. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
  8. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
  9. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  10. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  11. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  12. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  13. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +1 -1
  14. package/dist/analyzers/dataAccessAnalyzer.js +50 -24
  15. package/dist/analyzers/dataAccessAnalyzer.js.map +1 -1
  16. package/dist/analyzers/documentationAnalyzer.d.ts +4 -1
  17. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  18. package/dist/analyzers/documentationAnalyzer.js +210 -111
  19. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  20. package/dist/analyzers/dryAnalyzer.d.ts.map +1 -1
  21. package/dist/analyzers/dryAnalyzer.js +190 -58
  22. package/dist/analyzers/dryAnalyzer.js.map +1 -1
  23. package/dist/analyzers/invariantsAnalyzer.d.ts +20 -0
  24. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -0
  25. package/dist/analyzers/invariantsAnalyzer.js +148 -0
  26. package/dist/analyzers/invariantsAnalyzer.js.map +1 -0
  27. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  28. package/dist/analyzers/reactAnalyzer.js +2 -2
  29. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  30. package/dist/analyzers/schemaAnalyzer.d.ts +2 -0
  31. package/dist/analyzers/schemaAnalyzer.d.ts.map +1 -1
  32. package/dist/analyzers/schemaAnalyzer.js +306 -147
  33. package/dist/analyzers/schemaAnalyzer.js.map +1 -1
  34. package/dist/analyzers/solidAnalyzer.d.ts.map +1 -1
  35. package/dist/analyzers/solidAnalyzer.js +145 -119
  36. package/dist/analyzers/solidAnalyzer.js.map +1 -1
  37. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +3 -1
  38. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  39. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +41 -1
  40. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  41. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  42. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +8 -8
  43. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  44. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  45. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +6 -6
  46. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  47. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  48. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  49. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +1 -1
  50. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  51. package/dist/api/index.js +1 -0
  52. package/dist/api/index.js.map +1 -1
  53. package/dist/auditRunner.d.ts.map +1 -1
  54. package/dist/auditRunner.js +367 -24
  55. package/dist/auditRunner.js.map +1 -1
  56. package/dist/cli.d.ts +1 -1
  57. package/dist/cli.d.ts.map +1 -1
  58. package/dist/cli.js +740 -18
  59. package/dist/cli.js.map +1 -1
  60. package/dist/codeIndexDB-enhanced.d.ts +35 -13
  61. package/dist/codeIndexDB-enhanced.d.ts.map +1 -1
  62. package/dist/codeIndexDB-enhanced.js +311 -214
  63. package/dist/codeIndexDB-enhanced.js.map +1 -1
  64. package/dist/{codeIndexDb.d.ts → 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.map +1 -1
  254. package/dist/utils/jsDocParser.d.ts +27 -30
  255. package/dist/utils/jsDocParser.d.ts.map +1 -1
  256. package/dist/utils/jsDocParser.js +271 -207
  257. package/dist/utils/jsDocParser.js.map +1 -1
  258. package/dist/utils/reactDetection.d.ts +27 -12
  259. package/dist/utils/reactDetection.d.ts.map +1 -1
  260. package/dist/utils/reactDetection.js +331 -219
  261. package/dist/utils/reactDetection.js.map +1 -1
  262. package/dist/workers/auditWorker.js +17 -3
  263. package/dist/workers/auditWorker.js.map +1 -1
  264. package/package.json +54 -51
  265. package/dist/adapters/GoAdapter.d.ts +0 -53
  266. package/dist/adapters/GoAdapter.d.ts.map +0 -1
  267. package/dist/adapters/GoAdapter.js +0 -611
  268. package/dist/adapters/GoAdapter.js.map +0 -1
  269. package/dist/adapters/GoConfig.d.ts +0 -35
  270. package/dist/adapters/GoConfig.d.ts.map +0 -1
  271. package/dist/adapters/GoConfig.js +0 -155
  272. package/dist/adapters/GoConfig.js.map +0 -1
  273. package/dist/adapters/LanguageAdapter.d.ts +0 -135
  274. package/dist/adapters/LanguageAdapter.d.ts.map +0 -1
  275. package/dist/adapters/LanguageAdapter.js +0 -47
  276. package/dist/adapters/LanguageAdapter.js.map +0 -1
  277. package/dist/adapters/TypeScriptAdapter.d.ts +0 -43
  278. package/dist/adapters/TypeScriptAdapter.d.ts.map +0 -1
  279. package/dist/adapters/TypeScriptAdapter.js +0 -386
  280. package/dist/adapters/TypeScriptAdapter.js.map +0 -1
  281. package/dist/adapters/UniversalAnalyzer.d.ts +0 -42
  282. package/dist/adapters/UniversalAnalyzer.d.ts.map +0 -1
  283. package/dist/adapters/UniversalAnalyzer.js +0 -103
  284. package/dist/adapters/UniversalAnalyzer.js.map +0 -1
  285. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts +0 -48
  286. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts.map +0 -1
  287. package/dist/adapters/UniversalSOLIDAnalyzer.js +0 -178
  288. package/dist/adapters/UniversalSOLIDAnalyzer.js.map +0 -1
  289. package/dist/adapters/solidAnalyzerCompat.d.ts +0 -21
  290. package/dist/adapters/solidAnalyzerCompat.d.ts.map +0 -1
  291. package/dist/adapters/solidAnalyzerCompat.js +0 -78
  292. package/dist/adapters/solidAnalyzerCompat.js.map +0 -1
  293. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts +0 -20
  294. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts.map +0 -1
  295. package/dist/analyzers/dataAccessAnalyzerCompat.js +0 -59
  296. package/dist/analyzers/dataAccessAnalyzerCompat.js.map +0 -1
  297. package/dist/analyzers/documentationAnalyzerCompat.d.ts +0 -20
  298. package/dist/analyzers/documentationAnalyzerCompat.d.ts.map +0 -1
  299. package/dist/analyzers/documentationAnalyzerCompat.js +0 -52
  300. package/dist/analyzers/documentationAnalyzerCompat.js.map +0 -1
  301. package/dist/analyzers/dryAnalyzerCompat.d.ts +0 -20
  302. package/dist/analyzers/dryAnalyzerCompat.d.ts.map +0 -1
  303. package/dist/analyzers/dryAnalyzerCompat.js +0 -44
  304. package/dist/analyzers/dryAnalyzerCompat.js.map +0 -1
  305. package/dist/analyzers/schemaAnalyzerCompat.d.ts +0 -20
  306. package/dist/analyzers/schemaAnalyzerCompat.d.ts.map +0 -1
  307. package/dist/analyzers/schemaAnalyzerCompat.js +0 -69
  308. package/dist/analyzers/schemaAnalyzerCompat.js.map +0 -1
  309. package/dist/analyzers/solidAnalyzerCompat.d.ts +0 -20
  310. package/dist/analyzers/solidAnalyzerCompat.d.ts.map +0 -1
  311. package/dist/analyzers/solidAnalyzerCompat.js +0 -78
  312. package/dist/analyzers/solidAnalyzerCompat.js.map +0 -1
  313. package/dist/codeIndexDb.d.ts.map +0 -1
  314. package/dist/codeIndexDb.js +0 -2102
  315. package/dist/codeIndexDb.js.map +0 -1
  316. package/dist/languages/typescript/TypeScriptAdapter.d.ts +0 -108
  317. package/dist/languages/typescript/TypeScriptAdapter.d.ts.map +0 -1
  318. package/dist/languages/typescript/TypeScriptAdapter.js +0 -567
  319. package/dist/languages/typescript/TypeScriptAdapter.js.map +0 -1
  320. package/dist/languages/typescript/test-extraction.d.ts +0 -2
  321. package/dist/languages/typescript/test-extraction.d.ts.map +0 -1
  322. package/dist/languages/typescript/test-extraction.js +0 -36
  323. package/dist/languages/typescript/test-extraction.js.map +0 -1
  324. package/dist/mcp-hybrid.d.ts +0 -10
  325. package/dist/mcp-hybrid.d.ts.map +0 -1
  326. package/dist/mcp-hybrid.js +0 -41
  327. package/dist/mcp-hybrid.js.map +0 -1
  328. package/dist/mcp-index.d.ts +0 -13
  329. package/dist/mcp-index.d.ts.map +0 -1
  330. package/dist/mcp-index.js +0 -136
  331. package/dist/mcp-index.js.map +0 -1
  332. package/dist/mcp-standalone.d.ts +0 -3
  333. package/dist/mcp-standalone.d.ts.map +0 -1
  334. package/dist/mcp-standalone.js +0 -1324
  335. package/dist/mcp-standalone.js.map +0 -1
  336. package/dist/mcp-ui-server.d.ts +0 -27
  337. package/dist/mcp-ui-server.d.ts.map +0 -1
  338. package/dist/mcp-ui-server.js +0 -484
  339. package/dist/mcp-ui-server.js.map +0 -1
  340. package/dist/test-multi-strategy-search.d.ts +0 -3
  341. package/dist/test-multi-strategy-search.d.ts.map +0 -1
  342. package/dist/test-multi-strategy-search.js +0 -170
  343. package/dist/test-multi-strategy-search.js.map +0 -1
@@ -0,0 +1,2042 @@
1
+ /**
2
+ * Code Index Database using SQLite (better-sqlite3) + FTS5
3
+ * Replaces LokiJS + FlexSearch with durable, transactional storage.
4
+ */
5
+ import Database from 'better-sqlite3';
6
+ import { createHash } from 'crypto';
7
+ import { promises as fs } from 'fs';
8
+ import path from 'path';
9
+ import { ProjectTaskRepository } from './services/ProjectTaskRepository.js';
10
+ import { QueryParser, compileToSQL } 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
+ // ── Content hash ────────────────────────────────────────────────────────
15
+ function computeContentHash(body, signature) {
16
+ const normalized = (body ?? '').replace(/\s+/g, ' ').trim() + '|' + (signature ?? '').trim();
17
+ return createHash('sha256').update(normalized).digest('hex');
18
+ }
19
+ class SqliteCollectionAdapter {
20
+ db;
21
+ tableName;
22
+ constructor(db, tableName) {
23
+ this.db = db;
24
+ this.tableName = tableName;
25
+ }
26
+ /** Return all rows, or rows matching the query. */
27
+ find(query) {
28
+ if (!query) {
29
+ return this.db.prepare(`SELECT *, rowid as "$loki" FROM "${this.tableName}"`).all()
30
+ .map(r => this.unbindRow(r));
31
+ }
32
+ const clauses = [];
33
+ const params = {};
34
+ for (const [key, value] of Object.entries(query)) {
35
+ if (value === null || value === undefined) {
36
+ clauses.push(`"${key}" IS NULL`);
37
+ }
38
+ else if (key === '$loki' && typeof value === 'object' && value.$in) {
39
+ // Handle $loki: { $in: [...] }
40
+ const placeholders = value.$in.map((_, i) => `@in_${i}`);
41
+ clauses.push(`rowid IN (${placeholders.join(', ')})`);
42
+ value.$in.forEach((v, i) => { params[`in_${i}`] = this.bindable(v); });
43
+ }
44
+ else if (key === 'expiresAt' && typeof value === 'object' && value.$lt) {
45
+ clauses.push(`"expiresAt" < @expiresAt`);
46
+ params['expiresAt'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
47
+ }
48
+ else if (key === 'timestamp' && typeof value === 'object' && value.$lt) {
49
+ clauses.push(`"timestamp" < @timestamp`);
50
+ params['timestamp'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
51
+ }
52
+ else {
53
+ clauses.push(`"${key}" = @${key}`);
54
+ params[key] = this.bindable(value);
55
+ }
56
+ }
57
+ const sql = `SELECT *, rowid as "$loki" FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''}`;
58
+ return this.db.prepare(sql).all(params).map(r => this.unbindRow(r));
59
+ }
60
+ findOne(query) {
61
+ const clauses = [];
62
+ const params = {};
63
+ for (const [key, value] of Object.entries(query)) {
64
+ if (value === null || value === undefined) {
65
+ clauses.push(`"${key}" IS NULL`);
66
+ }
67
+ else {
68
+ clauses.push(`"${key}" = @${key}`);
69
+ params[key] = this.bindable(value);
70
+ }
71
+ }
72
+ const sql = `SELECT *, rowid as "$loki" FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''} LIMIT 1`;
73
+ const row = this.db.prepare(sql).get(params) ?? null;
74
+ return row ? this.unbindRow(row) : null;
75
+ }
76
+ /** Convert a doc value to something SQLite can bind (primitives + Buffer, null). */
77
+ bindable(v) {
78
+ if (v === null || v === undefined)
79
+ return null;
80
+ if (typeof v === 'object' && !Buffer.isBuffer(v))
81
+ return JSON.stringify(v);
82
+ return v;
83
+ }
84
+ /** Try to parse a value that looks like serialized JSON back to its native form. */
85
+ unbindable(v) {
86
+ if (typeof v === 'string' && (v.startsWith('[') || v.startsWith('{'))) {
87
+ try {
88
+ return JSON.parse(v);
89
+ }
90
+ catch { /* not JSON, leave as string */ }
91
+ }
92
+ return v;
93
+ }
94
+ unbindRow(row) {
95
+ if (!row)
96
+ return row;
97
+ const out = {};
98
+ for (const [k, v] of Object.entries(row)) {
99
+ out[k] = this.unbindable(v);
100
+ }
101
+ return out;
102
+ }
103
+ insert(doc) {
104
+ const keys = Object.keys(doc);
105
+ const vals = keys.map(k => `@${k}`);
106
+ const sql = `INSERT INTO "${this.tableName}" ("${keys.join('", "')}") VALUES (${vals.join(', ')})`;
107
+ const params = {};
108
+ for (const k of keys)
109
+ params[k] = this.bindable(doc[k]);
110
+ const info = this.db.prepare(sql).run(params);
111
+ return { ...doc, $loki: Number(info.lastInsertRowid) };
112
+ }
113
+ update(doc) {
114
+ const keys = Object.keys(doc).filter(k => k !== '$loki' && k !== 'meta');
115
+ const sets = keys.map(k => `"${k}" = @${k}`);
116
+ const params = {};
117
+ for (const k of keys)
118
+ params[k] = this.bindable(doc[k]);
119
+ params['_rowid'] = doc.$loki;
120
+ this.db.prepare(`UPDATE "${this.tableName}" SET ${sets.join(', ')} WHERE rowid = @_rowid`).run(params);
121
+ }
122
+ remove(doc) {
123
+ if (doc.$loki !== undefined) {
124
+ this.db.prepare(`DELETE FROM "${this.tableName}" WHERE rowid = @_rowid`).run({ _rowid: doc.$loki });
125
+ }
126
+ }
127
+ /** Remove all rows matching a query. */
128
+ findAndRemove(query) {
129
+ const clauses = [];
130
+ const params = {};
131
+ for (const [key, value] of Object.entries(query)) {
132
+ if (value === null || value === undefined) {
133
+ clauses.push(`"${key}" IS NULL`);
134
+ }
135
+ else if (key === 'timestamp' && typeof value === 'object' && value.$lt) {
136
+ clauses.push(`"timestamp" < @timestamp`);
137
+ params['timestamp'] = value.$lt instanceof Date ? value.$lt.toISOString() : String(value.$lt);
138
+ }
139
+ else {
140
+ clauses.push(`"${key}" = @${key}`);
141
+ params[key] = value;
142
+ }
143
+ }
144
+ this.db.prepare(`DELETE FROM "${this.tableName}"${clauses.length ? ' WHERE ' + clauses.join(' AND ') : ''}`).run(params);
145
+ }
146
+ clear() {
147
+ this.db.prepare(`DELETE FROM "${this.tableName}"`).run();
148
+ }
149
+ chain() {
150
+ return {
151
+ _table: this.tableName,
152
+ _db: this.db,
153
+ _query: null,
154
+ _result: null,
155
+ find(query) {
156
+ this._query = query ?? null;
157
+ return this;
158
+ },
159
+ where(fn) {
160
+ if (this._result === null) {
161
+ this._result = this._query !== null
162
+ ? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
163
+ : new SqliteCollectionAdapter(this._db, this._table).find();
164
+ }
165
+ this._result = this._result.filter(fn);
166
+ return this;
167
+ },
168
+ simplesort(field, opts = {}) {
169
+ if (this._result === null) {
170
+ this._result = this._query !== null
171
+ ? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
172
+ : new SqliteCollectionAdapter(this._db, this._table).find();
173
+ }
174
+ this._result.sort((a, b) => {
175
+ const av = a[field] ?? '';
176
+ const bv = b[field] ?? '';
177
+ if (av < bv)
178
+ return opts.desc ? 1 : -1;
179
+ if (av > bv)
180
+ return opts.desc ? -1 : 1;
181
+ return 0;
182
+ });
183
+ return this;
184
+ },
185
+ limit(n) {
186
+ if (this._result === null) {
187
+ this._result = this._query !== null
188
+ ? new SqliteCollectionAdapter(this._db, this._table).find(this._query)
189
+ : new SqliteCollectionAdapter(this._db, this._table).find();
190
+ }
191
+ this._result = this._result.slice(0, n);
192
+ return this;
193
+ },
194
+ data() {
195
+ return this._result ?? [];
196
+ }
197
+ };
198
+ }
199
+ }
200
+ // ── Main class ──────────────────────────────────────────────────────────
201
+ export class CodeIndexDB {
202
+ static instance;
203
+ /** Subclasses (e.g. EnhancedCodeIndexDB) need access for extra tables without `as any`. */
204
+ db;
205
+ dbPath;
206
+ isInitialized = false;
207
+ initializePromise = null;
208
+ // Collection adapters (preserve naming for internal clarity)
209
+ functionsAdapter;
210
+ whitelistAdapter;
211
+ auditResultsAdapter;
212
+ analyzerConfigAdapter;
213
+ codeMapAdapter;
214
+ schemaAdapter;
215
+ schemaUsageAdapter;
216
+ tasksAdapter;
217
+ taskRepository = null;
218
+ stmts = new Map();
219
+ // ── Schema version ──────────────────────────────────────────────────
220
+ static SCHEMA_VERSION = 1;
221
+ constructor(dbPath = ':memory:') {
222
+ this.dbPath = dbPath === ':memory:' ? dbPath : path.resolve(dbPath);
223
+ }
224
+ // ── Singleton ───────────────────────────────────────────────────────
225
+ static getInstance(dbPath) {
226
+ if (!CodeIndexDB.instance) {
227
+ const resolved = dbPath !== undefined && dbPath !== ''
228
+ ? path.resolve(dbPath)
229
+ : resolvePersistedIndexPath();
230
+ CodeIndexDB.instance = new CodeIndexDB(resolved);
231
+ }
232
+ return CodeIndexDB.instance;
233
+ }
234
+ // ── Lifecycle ───────────────────────────────────────────────────────
235
+ async initialize() {
236
+ if (this.isInitialized)
237
+ return;
238
+ if (this.initializePromise) {
239
+ await this.initializePromise;
240
+ return;
241
+ }
242
+ this.initializePromise = this.initializeInternal();
243
+ try {
244
+ await this.initializePromise;
245
+ }
246
+ finally {
247
+ this.initializePromise = null;
248
+ }
249
+ }
250
+ async initializeInternal() {
251
+ if (this.isInitialized)
252
+ return;
253
+ // Ensure parent directory exists
254
+ if (this.dbPath !== ':memory:') {
255
+ const dir = path.dirname(this.dbPath);
256
+ const storageRoot = getPersistedStorageRoot();
257
+ try {
258
+ let st;
259
+ try {
260
+ st = await fs.stat(dir);
261
+ }
262
+ catch (e) {
263
+ const code = getErrnoCode(e);
264
+ if (code && code !== 'ENOENT') {
265
+ throw new ContextualError(`Cannot access code index storage directory (${code}): ${dir}`, { errnoCode: code, storageRoot, dbPath: this.dbPath,
266
+ hint: 'Fix permissions or set CODE_AUDITOR_DATA_DIR / --data-dir to a writable directory.' }, e instanceof Error ? e : undefined);
267
+ }
268
+ }
269
+ if (st && !st.isDirectory()) {
270
+ throw new ContextualError(`Code index storage path exists but is not a directory: ${dir}`, { storageRoot, dbPath: this.dbPath,
271
+ hint: 'Remove the conflicting file/path or choose a different CODE_AUDITOR_DATA_DIR / --data-dir.' });
272
+ }
273
+ await fs.mkdir(dir, { recursive: true });
274
+ }
275
+ catch (e) {
276
+ if (e instanceof ContextualError)
277
+ throw e;
278
+ const code = getErrnoCode(e);
279
+ throw new ContextualError(`Failed to prepare code index storage: ${e instanceof Error ? e.message : String(e)}`, { ...(code && { errnoCode: code }), storageRoot, dbPath: this.dbPath,
280
+ hint: 'Ensure the storage directory is writable (default: <cwd>/.code-index when CODE_AUDITOR_DATA_DIR is unset).' }, e instanceof Error ? e : undefined);
281
+ }
282
+ }
283
+ // Check for LokiJS migration
284
+ const migrationResult = this.maybeMigrateFromLokiJS();
285
+ // Open SQLite database
286
+ try {
287
+ this.db = new Database(this.dbPath);
288
+ this.db.pragma('journal_mode = WAL');
289
+ this.db.pragma('foreign_keys = ON');
290
+ }
291
+ catch (e) {
292
+ const code = getErrnoCode(e);
293
+ throw new ContextualError(`Failed to open code index database: ${e instanceof Error ? e.message : String(e)}`, { ...(code && { errnoCode: code }), dbPath: this.dbPath,
294
+ hint: 'The index file may be corrupted, locked, or on a read-only volume. Try a different CODE_AUDITOR_DATA_DIR.' }, e instanceof Error ? e : undefined);
295
+ }
296
+ // Create schema
297
+ this.createSchema();
298
+ // Initialize collection adapters
299
+ this.functionsAdapter = new SqliteCollectionAdapter(this.db, 'functions');
300
+ this.whitelistAdapter = new SqliteCollectionAdapter(this.db, 'whitelist');
301
+ this.auditResultsAdapter = new SqliteCollectionAdapter(this.db, 'audit_results');
302
+ this.analyzerConfigAdapter = new SqliteCollectionAdapter(this.db, 'analyzer_configs');
303
+ this.codeMapAdapter = new SqliteCollectionAdapter(this.db, 'code_maps');
304
+ this.schemaAdapter = new SqliteCollectionAdapter(this.db, 'schema_definitions');
305
+ this.schemaUsageAdapter = new SqliteCollectionAdapter(this.db, 'schema_usage');
306
+ this.tasksAdapter = new SqliteCollectionAdapter(this.db, 'project_tasks');
307
+ // Init defaults
308
+ const whitelistCount = this.db.prepare('SELECT COUNT(*) as cnt FROM whitelist').get().cnt;
309
+ if (whitelistCount === 0) {
310
+ await this.initializeDefaultWhitelists();
311
+ }
312
+ // Auto-sync if we just migrated
313
+ if (migrationResult.migrated) {
314
+ const funcCount = this.db.prepare('SELECT COUNT(*) as cnt FROM functions').get().cnt;
315
+ if (funcCount === 0) {
316
+ try {
317
+ await this.deepSync();
318
+ }
319
+ catch {
320
+ // Silently skip — index will be rebuilt on next sync
321
+ }
322
+ }
323
+ }
324
+ this.isInitialized = true;
325
+ }
326
+ // ── SQLite schema ───────────────────────────────────────────────────
327
+ createSchema() {
328
+ this.db.exec(`
329
+ CREATE TABLE IF NOT EXISTS meta (
330
+ key TEXT PRIMARY KEY,
331
+ value TEXT NOT NULL
332
+ );
333
+
334
+ CREATE TABLE IF NOT EXISTS functions (
335
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
336
+ name TEXT NOT NULL,
337
+ file_path TEXT NOT NULL,
338
+ line_number INTEGER,
339
+ start_line INTEGER,
340
+ end_line INTEGER,
341
+ language TEXT DEFAULT 'typescript',
342
+ entity_type TEXT DEFAULT 'function',
343
+ component_type TEXT,
344
+ signature TEXT,
345
+ return_type TEXT,
346
+ complexity INTEGER DEFAULT 0,
347
+ is_exported INTEGER DEFAULT 0,
348
+ has_jsdoc INTEGER DEFAULT 0,
349
+ jsdoc_description TEXT,
350
+ jsdoc_tags TEXT,
351
+ parameters TEXT,
352
+ type_info TEXT,
353
+ hooks TEXT,
354
+ props TEXT,
355
+ used_imports TEXT,
356
+ unused_imports TEXT,
357
+ import_usage TEXT,
358
+ has_unused_imports INTEGER DEFAULT 0,
359
+ dependency_depth INTEGER DEFAULT 0,
360
+ purpose TEXT DEFAULT '',
361
+ context TEXT DEFAULT '',
362
+ body TEXT,
363
+ content_hash TEXT,
364
+ last_modified TEXT,
365
+ metadata_json TEXT,
366
+ created_at TEXT DEFAULT (datetime('now')),
367
+ updated_at TEXT DEFAULT (datetime('now'))
368
+ );
369
+
370
+ CREATE INDEX IF NOT EXISTS idx_functions_name ON functions(name);
371
+ CREATE INDEX IF NOT EXISTS idx_functions_file_path ON functions(file_path);
372
+ CREATE INDEX IF NOT EXISTS idx_functions_language ON functions(language);
373
+ CREATE INDEX IF NOT EXISTS idx_functions_entity_type ON functions(entity_type);
374
+ CREATE INDEX IF NOT EXISTS idx_functions_complexity ON functions(complexity);
375
+ CREATE INDEX IF NOT EXISTS idx_functions_content_hash ON functions(content_hash);
376
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_functions_name_file ON functions(name, file_path);
377
+
378
+ CREATE VIRTUAL TABLE IF NOT EXISTS functions_fts USING fts5(
379
+ name, signature, jsdoc_description, purpose, context, body,
380
+ content='functions', content_rowid='id',
381
+ tokenize='porter unicode61'
382
+ );
383
+
384
+ CREATE TRIGGER IF NOT EXISTS functions_ai AFTER INSERT ON functions BEGIN
385
+ INSERT INTO functions_fts(rowid, name, signature, jsdoc_description, purpose, context, body)
386
+ VALUES (new.id, new.name, new.signature, new.jsdoc_description, new.purpose, new.context, new.body);
387
+ END;
388
+ CREATE TRIGGER IF NOT EXISTS functions_ad AFTER DELETE ON functions BEGIN
389
+ INSERT INTO functions_fts(functions_fts, rowid, name, signature, jsdoc_description, purpose, context, body)
390
+ VALUES ('delete', old.id, old.name, old.signature, old.jsdoc_description, old.purpose, old.context, old.body);
391
+ END;
392
+ CREATE TRIGGER IF NOT EXISTS functions_au AFTER UPDATE ON functions BEGIN
393
+ INSERT INTO functions_fts(functions_fts, rowid, name, signature, jsdoc_description, purpose, context, body)
394
+ VALUES ('delete', old.id, old.name, old.signature, old.jsdoc_description, old.purpose, old.context, old.body);
395
+ INSERT INTO functions_fts(rowid, name, signature, jsdoc_description, purpose, context, body)
396
+ VALUES (new.id, new.name, new.signature, new.jsdoc_description, new.purpose, new.context, new.body);
397
+ END;
398
+
399
+ CREATE TABLE IF NOT EXISTS function_calls (
400
+ caller_id INTEGER NOT NULL REFERENCES functions(id) ON DELETE CASCADE,
401
+ callee_name TEXT NOT NULL,
402
+ PRIMARY KEY (caller_id, callee_name)
403
+ );
404
+ CREATE INDEX IF NOT EXISTS idx_function_calls_callee ON function_calls(callee_name);
405
+
406
+ CREATE TABLE IF NOT EXISTS function_dependencies (
407
+ function_id INTEGER NOT NULL REFERENCES functions(id) ON DELETE CASCADE,
408
+ dependency TEXT NOT NULL,
409
+ PRIMARY KEY (function_id, dependency)
410
+ );
411
+ CREATE INDEX IF NOT EXISTS idx_function_dependencies_dep ON function_dependencies(dependency);
412
+
413
+ CREATE TABLE IF NOT EXISTS whitelist (
414
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
415
+ name TEXT NOT NULL,
416
+ type TEXT NOT NULL,
417
+ status TEXT DEFAULT 'Active',
418
+ category TEXT,
419
+ description TEXT,
420
+ patterns TEXT,
421
+ added_by TEXT DEFAULT 'system',
422
+ added_at TEXT DEFAULT (datetime('now')),
423
+ updated_at TEXT,
424
+ metadata_json TEXT
425
+ );
426
+ CREATE INDEX IF NOT EXISTS idx_whitelist_name ON whitelist(name);
427
+ CREATE INDEX IF NOT EXISTS idx_whitelist_type ON whitelist(type);
428
+ CREATE INDEX IF NOT EXISTS idx_whitelist_status ON whitelist(status);
429
+
430
+ CREATE TABLE IF NOT EXISTS audit_results (
431
+ audit_id TEXT PRIMARY KEY,
432
+ timestamp TEXT NOT NULL,
433
+ project_path TEXT NOT NULL,
434
+ summary_json TEXT NOT NULL,
435
+ analyzer_results_json TEXT NOT NULL,
436
+ violations_json TEXT,
437
+ recommendations_json TEXT,
438
+ metadata_json TEXT,
439
+ expires_at TEXT NOT NULL
440
+ );
441
+ CREATE INDEX IF NOT EXISTS idx_audit_results_timestamp ON audit_results(timestamp);
442
+ CREATE INDEX IF NOT EXISTS idx_audit_results_project_path ON audit_results(project_path);
443
+
444
+ CREATE TABLE IF NOT EXISTS analyzer_configs (
445
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
446
+ analyzer_name TEXT NOT NULL,
447
+ project_path TEXT,
448
+ is_global INTEGER DEFAULT 0,
449
+ config_json TEXT NOT NULL DEFAULT '{}',
450
+ version TEXT,
451
+ created_by TEXT DEFAULT 'system',
452
+ created_at TEXT DEFAULT (datetime('now')),
453
+ updated_at TEXT DEFAULT (datetime('now')),
454
+ metadata_json TEXT
455
+ );
456
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_analyzer_configs_key
457
+ ON analyzer_configs(analyzer_name, COALESCE(project_path, '__global__'), is_global);
458
+
459
+ CREATE TABLE IF NOT EXISTS code_maps (
460
+ map_id TEXT NOT NULL,
461
+ section_type TEXT NOT NULL,
462
+ content TEXT NOT NULL,
463
+ metadata_json TEXT DEFAULT '{}',
464
+ timestamp TEXT DEFAULT (datetime('now')),
465
+ size INTEGER DEFAULT 0,
466
+ PRIMARY KEY (map_id, section_type)
467
+ );
468
+ CREATE INDEX IF NOT EXISTS idx_code_maps_timestamp ON code_maps(timestamp);
469
+
470
+ CREATE TABLE IF NOT EXISTS schema_definitions (
471
+ schema_id TEXT PRIMARY KEY,
472
+ schema_name TEXT,
473
+ schema_json TEXT NOT NULL,
474
+ metadata_json TEXT,
475
+ indexed_at TEXT DEFAULT (datetime('now'))
476
+ );
477
+
478
+ CREATE TABLE IF NOT EXISTS schema_usage (
479
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
480
+ schema_id TEXT,
481
+ table_name TEXT NOT NULL,
482
+ file_path TEXT NOT NULL,
483
+ function_name TEXT NOT NULL,
484
+ usage_type TEXT NOT NULL,
485
+ line INTEGER,
486
+ "column" INTEGER,
487
+ raw_query TEXT,
488
+ parameters TEXT,
489
+ recorded_at TEXT DEFAULT (datetime('now'))
490
+ );
491
+ CREATE INDEX IF NOT EXISTS idx_schema_usage_table ON schema_usage(table_name);
492
+ CREATE INDEX IF NOT EXISTS idx_schema_usage_file ON schema_usage(file_path);
493
+ CREATE INDEX IF NOT EXISTS idx_schema_usage_function ON schema_usage(function_name);
494
+
495
+ CREATE TABLE IF NOT EXISTS project_tasks (
496
+ taskId TEXT PRIMARY KEY,
497
+ projectPath TEXT NOT NULL,
498
+ title TEXT NOT NULL,
499
+ description TEXT DEFAULT '',
500
+ status TEXT DEFAULT 'pending',
501
+ priority TEXT DEFAULT 'medium',
502
+ labels TEXT DEFAULT '[]',
503
+ source TEXT DEFAULT 'manual',
504
+ parentTaskId TEXT,
505
+ blockedBy TEXT DEFAULT '[]',
506
+ dueAt TEXT,
507
+ sortOrder INTEGER DEFAULT 0,
508
+ relatedFiles TEXT DEFAULT '[]',
509
+ relatedSymbols TEXT DEFAULT '[]',
510
+ fingerprint TEXT,
511
+ metadata TEXT DEFAULT '{}',
512
+ createdAt TEXT DEFAULT (datetime('now')),
513
+ updatedAt TEXT DEFAULT (datetime('now')),
514
+ completedAt TEXT
515
+ );
516
+ CREATE INDEX IF NOT EXISTS idx_project_tasks_project ON project_tasks(projectPath);
517
+ CREATE INDEX IF NOT EXISTS idx_project_tasks_status ON project_tasks(status);
518
+ CREATE INDEX IF NOT EXISTS idx_project_tasks_source ON project_tasks(source);
519
+ CREATE INDEX IF NOT EXISTS idx_project_tasks_fingerprint ON project_tasks(fingerprint);
520
+ CREATE INDEX IF NOT EXISTS idx_project_tasks_sort ON project_tasks(sortOrder);
521
+ `);
522
+ // Record schema version
523
+ this.db.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)`).run(String(CodeIndexDB.SCHEMA_VERSION));
524
+ }
525
+ // ── LokiJS migration ────────────────────────────────────────────────
526
+ maybeMigrateFromLokiJS() {
527
+ if (this.dbPath === ':memory:')
528
+ return { migrated: false };
529
+ const bakPath = this.dbPath + '.loki.bak';
530
+ // If backup already exists alongside a valid SQLite db, skip
531
+ try {
532
+ const st = require('fs').statSync(bakPath);
533
+ if (st.isFile()) {
534
+ // Check if SQLite db exists
535
+ try {
536
+ require('fs').statSync(this.dbPath);
537
+ return { migrated: false };
538
+ }
539
+ catch { /* fall through */ }
540
+ }
541
+ }
542
+ catch { /* no backup */ }
543
+ // Check if dbPath is a LokiJS file
544
+ try {
545
+ const raw = require('fs').readFileSync(this.dbPath, 'utf-8');
546
+ if (!raw.startsWith('{"filename":') && !raw.includes('"collections":')) {
547
+ return { migrated: false };
548
+ }
549
+ }
550
+ catch {
551
+ return { migrated: false };
552
+ }
553
+ // It's LokiJS — migrate
554
+ try {
555
+ const content = require('fs').readFileSync(this.dbPath, 'utf-8');
556
+ const data = JSON.parse(content);
557
+ // LokiJS stores collections as an array of {name, data, ...}
558
+ const collArray = Array.isArray(data.collections)
559
+ ? data.collections
560
+ : [];
561
+ const collections = {};
562
+ for (const c of collArray) {
563
+ collections[c.name] = c.data ?? [];
564
+ }
565
+ // Rename old LokiJS file FIRST, then create fresh SQLite DB
566
+ require('fs').renameSync(this.dbPath, bakPath);
567
+ const migDb = new Database(this.dbPath);
568
+ migDb.pragma('journal_mode = WAL');
569
+ migDb.pragma('foreign_keys = ON');
570
+ // User-authored tables only (function index gets rebuilt by sync).
571
+ // whitelist + analyzer_configs use snake_case (matching raw SQL queries in the main code).
572
+ // project_tasks uses camelCase (matching ProjectTaskRepository document fields).
573
+ migDb.exec(`
574
+ CREATE TABLE IF NOT EXISTS whitelist (
575
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
576
+ name TEXT NOT NULL,
577
+ type TEXT NOT NULL,
578
+ status TEXT DEFAULT 'Active',
579
+ category TEXT,
580
+ description TEXT,
581
+ patterns TEXT,
582
+ added_by TEXT DEFAULT 'system',
583
+ added_at TEXT DEFAULT (datetime('now')),
584
+ updated_at TEXT,
585
+ metadata_json TEXT
586
+ );
587
+
588
+ CREATE TABLE IF NOT EXISTS analyzer_configs (
589
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
590
+ analyzer_name TEXT NOT NULL,
591
+ project_path TEXT,
592
+ is_global INTEGER DEFAULT 0,
593
+ config_json TEXT NOT NULL DEFAULT '{}',
594
+ version TEXT,
595
+ created_by TEXT DEFAULT 'system',
596
+ created_at TEXT DEFAULT (datetime('now')),
597
+ updated_at TEXT DEFAULT (datetime('now')),
598
+ metadata_json TEXT
599
+ );
600
+
601
+ CREATE TABLE IF NOT EXISTS project_tasks (
602
+ taskId TEXT PRIMARY KEY,
603
+ projectPath TEXT NOT NULL,
604
+ title TEXT NOT NULL,
605
+ description TEXT DEFAULT '',
606
+ status TEXT DEFAULT 'pending',
607
+ priority TEXT DEFAULT 'medium',
608
+ labels TEXT DEFAULT '[]',
609
+ source TEXT DEFAULT 'manual',
610
+ parentTaskId TEXT,
611
+ blockedBy TEXT DEFAULT '[]',
612
+ dueAt TEXT,
613
+ sortOrder INTEGER DEFAULT 0,
614
+ relatedFiles TEXT DEFAULT '[]',
615
+ relatedSymbols TEXT DEFAULT '[]',
616
+ fingerprint TEXT,
617
+ metadata TEXT DEFAULT '{}',
618
+ createdAt TEXT DEFAULT (datetime('now')),
619
+ updatedAt TEXT DEFAULT (datetime('now')),
620
+ completedAt TEXT
621
+ );
622
+ CREATE INDEX IF NOT EXISTS idx_project_tasks_fingerprint ON project_tasks(fingerprint);
623
+ `);
624
+ let taskCount = 0;
625
+ let configCount = 0;
626
+ let whitelistCount = 0;
627
+ // Migrate tasks
628
+ const tasksData = collections['projectTasks'] ?? [];
629
+ const insertTask = migDb.prepare(`INSERT OR IGNORE INTO project_tasks
630
+ (taskId, projectPath, title, description, status, priority, labels, source,
631
+ parentTaskId, blockedBy, dueAt, sortOrder, relatedFiles, relatedSymbols,
632
+ fingerprint, metadata, createdAt, updatedAt, completedAt)
633
+ VALUES (@taskId, @projectPath, @title, @description, @status, @priority, @labels, @source,
634
+ @parentTaskId, @blockedBy, @dueAt, @sortOrder, @relatedFiles, @relatedSymbols,
635
+ @fingerprint, @metadata, @createdAt, @updatedAt, @completedAt)`);
636
+ for (const t of tasksData) {
637
+ insertTask.run({
638
+ taskId: t.taskId ?? '',
639
+ projectPath: t.projectPath ?? '',
640
+ title: t.title ?? '',
641
+ description: t.description ?? '',
642
+ status: t.status ?? 'pending',
643
+ priority: t.priority ?? 'medium',
644
+ labels: JSON.stringify(t.labels ?? []),
645
+ source: t.source ?? 'manual',
646
+ parentTaskId: t.parentTaskId ?? null,
647
+ blockedBy: JSON.stringify(t.blockedBy ?? []),
648
+ dueAt: t.dueAt ?? null,
649
+ sortOrder: t.sortOrder ?? 0,
650
+ relatedFiles: JSON.stringify(t.relatedFiles ?? []),
651
+ relatedSymbols: JSON.stringify(t.relatedSymbols ?? []),
652
+ fingerprint: t.fingerprint ?? null,
653
+ metadata: JSON.stringify(t.metadata ?? t.metadata_json ?? {}),
654
+ createdAt: t.createdAt ?? t.created_at ?? new Date().toISOString(),
655
+ updatedAt: t.updatedAt ?? t.updated_at ?? new Date().toISOString(),
656
+ completedAt: t.completedAt ?? t.completed_at ?? null,
657
+ });
658
+ taskCount++;
659
+ }
660
+ // Migrate analyzer configs (snake_case columns matching createSchema)
661
+ const configsData = collections['analyzerConfigs'] ?? [];
662
+ const insertConfig = migDb.prepare(`INSERT OR IGNORE INTO analyzer_configs
663
+ (analyzer_name, project_path, is_global, config_json, version, created_by, created_at, updated_at, metadata_json)
664
+ VALUES (@analyzerName, @projectPath, @isGlobal, @configJson, @version, @createdBy, @createdAt, @updatedAt, @metadataJson)`);
665
+ for (const c of configsData) {
666
+ insertConfig.run({
667
+ analyzerName: c.analyzerName ?? '',
668
+ projectPath: c.projectPath ?? null,
669
+ isGlobal: c.isGlobal ? 1 : 0,
670
+ configJson: typeof c.config_json === 'string'
671
+ ? c.config_json
672
+ : JSON.stringify(c.config ?? c.config_json ?? {}),
673
+ version: c.version ?? null,
674
+ createdBy: c.createdBy ?? c.created_by ?? 'system',
675
+ createdAt: c.createdAt ?? c.created_at ?? new Date().toISOString(),
676
+ updatedAt: c.updatedAt ?? c.updated_at ?? new Date().toISOString(),
677
+ metadataJson: JSON.stringify(c.metadata ?? c.metadata_json ?? {}),
678
+ });
679
+ configCount++;
680
+ }
681
+ // Migrate whitelist (snake_case columns matching createSchema)
682
+ const whitelistData = collections['whitelist'] ?? [];
683
+ const insertWl = migDb.prepare(`INSERT OR IGNORE INTO whitelist
684
+ (name, type, status, category, description, patterns, added_by, added_at, updated_at, metadata_json)
685
+ VALUES (@name, @type, @status, @category, @description, @patterns, @addedBy, @addedAt, @updatedAt, @metadataJson)`);
686
+ for (const w of whitelistData) {
687
+ insertWl.run({
688
+ name: w.name ?? '',
689
+ type: w.type ?? 'PlatformAPI',
690
+ status: w.status ?? 'Active',
691
+ category: w.category ?? null,
692
+ description: w.description ?? null,
693
+ patterns: JSON.stringify(w.patterns ?? []),
694
+ addedBy: w.addedBy ?? w.added_by ?? 'system',
695
+ addedAt: w.addedAt ?? w.added_at ?? new Date().toISOString(),
696
+ updatedAt: w.updatedAt ?? w.updated_at ?? null,
697
+ metadataJson: JSON.stringify(w.metadata ?? w.metadata_json ?? {}),
698
+ });
699
+ whitelistCount++;
700
+ }
701
+ migDb.close();
702
+ const countLine = [
703
+ taskCount && `${taskCount} tasks`,
704
+ configCount && `${configCount} analyzer configs`,
705
+ whitelistCount && `${whitelistCount} whitelist entries`,
706
+ ].filter(Boolean).join(', ') || '0 entries';
707
+ console.log(`[code-auditor] Migrated ${countLine} from LokiJS to SQLite. Old file saved as ${path.basename(bakPath)}`);
708
+ return { migrated: true, counts: { tasks: taskCount, configs: configCount, whitelist: whitelistCount } };
709
+ }
710
+ catch (err) {
711
+ // Migration failed — try to restore the backup
712
+ console.error('[code-auditor] LokiJS migration failed:', err instanceof Error ? err.message : String(err));
713
+ try {
714
+ if (require('fs').existsSync(bakPath) && !require('fs').existsSync(this.dbPath)) {
715
+ require('fs').renameSync(bakPath, this.dbPath);
716
+ }
717
+ }
718
+ catch { /* best effort */ }
719
+ return { migrated: false };
720
+ }
721
+ }
722
+ // ── Init guard ──────────────────────────────────────────────────────
723
+ ensureInitialized() {
724
+ if (!this.isInitialized) {
725
+ throw new Error('Database not initialized. Call initialize() first.');
726
+ }
727
+ }
728
+ // ── Helpers: row → EnhancedFunctionMetadata ─────────────────────────
729
+ rowToFunction(row) {
730
+ return {
731
+ name: row.name,
732
+ filePath: row.file_path,
733
+ lineNumber: row.line_number,
734
+ startLine: row.start_line,
735
+ endLine: row.end_line,
736
+ language: row.language,
737
+ dependencies: [],
738
+ purpose: row.purpose ?? '',
739
+ context: row.context ?? '',
740
+ signature: row.signature ?? '',
741
+ parameters: tryParseJson(row.parameters) ?? [],
742
+ jsDoc: { description: row.jsdoc_description ?? '' },
743
+ imports: [],
744
+ body: row.body ?? '',
745
+ comments: [],
746
+ isAsync: false,
747
+ isGenerator: false,
748
+ returnType: row.return_type,
749
+ visibility: 'public',
750
+ complexity: row.complexity ?? 0,
751
+ content_hash: row.content_hash,
752
+ metadata: tryParseJson(row.metadata_json) ?? {},
753
+ hooks: tryParseJson(row.hooks),
754
+ props: tryParseJson(row.props),
755
+ };
756
+ }
757
+ functionToRow(func) {
758
+ const enhanced = func;
759
+ const jsDoc = func.jsDoc;
760
+ return {
761
+ name: func.name,
762
+ file_path: func.filePath,
763
+ line_number: func.lineNumber ?? null,
764
+ start_line: func.startLine ?? null,
765
+ end_line: func.endLine ?? null,
766
+ language: func.language ?? 'typescript',
767
+ entity_type: func.metadata?.entityType ?? 'function',
768
+ component_type: func.metadata?.componentType ?? null,
769
+ signature: enhanced.signature ?? '',
770
+ return_type: enhanced.returnType ?? null,
771
+ complexity: enhanced.complexity ?? func.metadata?.complexity ?? 0,
772
+ is_exported: func.metadata?.isExported ? 1 : 0,
773
+ has_jsdoc: (jsDoc && jsDoc.description) ? 1 : 0,
774
+ jsdoc_description: typeof jsDoc === 'string' ? jsDoc : (jsDoc?.description ?? ''),
775
+ jsdoc_tags: jsDoc?.tags ? JSON.stringify(jsDoc.tags) : null,
776
+ parameters: enhanced.parameters ? JSON.stringify(enhanced.parameters) : null,
777
+ type_info: func.metadata?.typeInfo ? JSON.stringify(func.metadata.typeInfo) : null,
778
+ hooks: enhanced.hooks ? JSON.stringify(enhanced.hooks) : null,
779
+ props: enhanced.props ? JSON.stringify(enhanced.props) : null,
780
+ used_imports: func.metadata?.usedImports ? JSON.stringify(func.metadata.usedImports) : null,
781
+ unused_imports: func.metadata?.unusedImports ? JSON.stringify(func.metadata.unusedImports) : null,
782
+ import_usage: func.metadata?.importUsage ? JSON.stringify(func.metadata.importUsage) : null,
783
+ has_unused_imports: func.metadata?.unusedImports?.length > 0 ? 1 : 0,
784
+ dependency_depth: func.metadata?.dependencyDepth ?? 0,
785
+ purpose: func.purpose ?? '',
786
+ context: func.context ?? '',
787
+ body: func.body ?? func.metadata?.body ?? null,
788
+ content_hash: enhanced.content_hash ?? computeContentHash(func.body ?? func.metadata?.body, enhanced.signature),
789
+ last_modified: new Date().toISOString(),
790
+ metadata_json: func.metadata ? JSON.stringify(func.metadata) : '{}',
791
+ };
792
+ }
793
+ // ── Function CRUD ───────────────────────────────────────────────────
794
+ async registerFunction(func) {
795
+ this.ensureInitialized();
796
+ const row = this.functionToRow(func);
797
+ try {
798
+ this.db.prepare(`
799
+ INSERT INTO functions (name, file_path, line_number, start_line, end_line, language,
800
+ entity_type, component_type, signature, return_type, complexity, is_exported, has_jsdoc,
801
+ jsdoc_description, jsdoc_tags, parameters, type_info, hooks, props,
802
+ used_imports, unused_imports, import_usage, has_unused_imports, dependency_depth,
803
+ purpose, context, body, content_hash, last_modified, metadata_json)
804
+ VALUES (@name, @file_path, @line_number, @start_line, @end_line, @language,
805
+ @entity_type, @component_type, @signature, @return_type, @complexity, @is_exported, @has_jsdoc,
806
+ @jsdoc_description, @jsdoc_tags, @parameters, @type_info, @hooks, @props,
807
+ @used_imports, @unused_imports, @import_usage, @has_unused_imports, @dependency_depth,
808
+ @purpose, @context, @body, @content_hash, @last_modified, @metadata_json)
809
+ ON CONFLICT(name, file_path) DO UPDATE SET
810
+ line_number=excluded.line_number, start_line=excluded.start_line, end_line=excluded.end_line,
811
+ language=excluded.language, entity_type=excluded.entity_type, component_type=excluded.component_type,
812
+ signature=excluded.signature, return_type=excluded.return_type, complexity=excluded.complexity,
813
+ is_exported=excluded.is_exported, has_jsdoc=excluded.has_jsdoc,
814
+ jsdoc_description=excluded.jsdoc_description, jsdoc_tags=excluded.jsdoc_tags,
815
+ parameters=excluded.parameters, type_info=excluded.type_info, hooks=excluded.hooks, props=excluded.props,
816
+ used_imports=excluded.used_imports, unused_imports=excluded.unused_imports,
817
+ import_usage=excluded.import_usage, has_unused_imports=excluded.has_unused_imports,
818
+ dependency_depth=excluded.dependency_depth,
819
+ purpose=excluded.purpose, context=excluded.context, body=excluded.body,
820
+ content_hash=excluded.content_hash, last_modified=excluded.last_modified,
821
+ metadata_json=excluded.metadata_json
822
+ `).run(row);
823
+ }
824
+ catch (error) {
825
+ throw new Error(`Failed to register function: ${error instanceof Error ? error.message : 'Unknown error'}`);
826
+ }
827
+ }
828
+ async registerFunctions(functions) {
829
+ this.ensureInitialized();
830
+ let registered = 0;
831
+ let failed = 0;
832
+ const errors = [];
833
+ const insertAll = this.db.transaction((funcs) => {
834
+ for (const func of funcs) {
835
+ try {
836
+ this.registerFunction(func);
837
+ registered++;
838
+ }
839
+ catch (error) {
840
+ failed++;
841
+ errors.push({
842
+ function: func.name || 'unknown',
843
+ error: error instanceof Error ? error.message : 'Unknown error'
844
+ });
845
+ }
846
+ }
847
+ });
848
+ insertAll(functions);
849
+ return {
850
+ success: failed === 0,
851
+ registered,
852
+ failed,
853
+ errors: errors
854
+ };
855
+ }
856
+ async syncFileIndex(filePath, currentFunctions) {
857
+ this.ensureInitialized();
858
+ const stats = { added: 0, updated: 0, removed: 0 };
859
+ const txn = this.db.transaction(() => {
860
+ const existing = this.db.prepare('SELECT id, name, file_path, line_number FROM functions WHERE file_path = ?').all(filePath);
861
+ const createKey = (f) => `${f.name ?? f.name}:${f.filePath ?? f.file_path}:${f.lineNumber ?? f.line_number}`;
862
+ const currentMap = new Map(currentFunctions.map(f => [createKey(f), f]));
863
+ // Insert/update current functions
864
+ for (const func of currentFunctions) {
865
+ const exists = existing.find(e => e.name === func.name);
866
+ if (exists) {
867
+ const row = this.functionToRow(func);
868
+ const keys = Object.keys(row);
869
+ const sets = keys.filter(k => k !== 'name' && k !== 'file_path').map(k => `"${k}" = @${k}`);
870
+ const params = { ...row, _id: exists.id };
871
+ this.db.prepare(`UPDATE functions SET ${sets.join(', ')} WHERE id = @_id`).run(params);
872
+ stats.updated++;
873
+ }
874
+ else {
875
+ const row = this.functionToRow(func);
876
+ const keys = Object.keys(row);
877
+ const sql = `INSERT INTO functions ("${keys.join('", "')}") VALUES (${keys.map(k => '@' + k).join(', ')})`;
878
+ this.db.prepare(sql).run(row);
879
+ stats.added++;
880
+ }
881
+ }
882
+ // Remove stale functions
883
+ for (const e of existing) {
884
+ if (!currentMap.has(createKey(e))) {
885
+ this.db.prepare('DELETE FROM functions WHERE id = ?').run(e.id);
886
+ stats.removed++;
887
+ }
888
+ }
889
+ });
890
+ txn();
891
+ await this.updateDependencyGraph(filePath);
892
+ return stats;
893
+ }
894
+ // ── Dependency graph ────────────────────────────────────────────────
895
+ async updateDependencyGraph(filePath) {
896
+ this.ensureInitialized();
897
+ const txn = this.db.transaction(() => {
898
+ // For functions in scope, rebuild their call edges
899
+ const functions = filePath
900
+ ? this.db.prepare('SELECT id, name, file_path FROM functions WHERE file_path = ?').all(filePath)
901
+ : this.db.prepare('SELECT id, name, file_path FROM functions').all();
902
+ // Clear existing call edges for scoped functions
903
+ if (filePath) {
904
+ this.db.prepare(`DELETE FROM function_calls WHERE caller_id IN (SELECT id FROM functions WHERE file_path = ?)`).run(filePath);
905
+ }
906
+ else {
907
+ this.db.prepare('DELETE FROM function_calls').run();
908
+ }
909
+ // Clear existing dependency edges for scoped functions
910
+ if (filePath) {
911
+ this.db.prepare(`DELETE FROM function_dependencies WHERE function_id IN (SELECT id FROM functions WHERE file_path = ?)`).run(filePath);
912
+ }
913
+ else {
914
+ this.db.prepare('DELETE FROM function_dependencies').run();
915
+ }
916
+ // Rebuild from metadata
917
+ const allFns = filePath
918
+ ? this.db.prepare('SELECT id, name, file_path, metadata_json FROM functions').all()
919
+ : this.db.prepare('SELECT id, name, file_path, metadata_json FROM functions').all();
920
+ const insertCall = this.db.prepare('INSERT OR IGNORE INTO function_calls (caller_id, callee_name) VALUES (?, ?)');
921
+ const insertDep = this.db.prepare('INSERT OR IGNORE INTO function_dependencies (function_id, dependency) VALUES (?, ?)');
922
+ for (const fn of allFns) {
923
+ const meta = tryParseJson(fn.metadata_json) ?? {};
924
+ if (meta.functionCalls) {
925
+ for (const callee of meta.functionCalls) {
926
+ insertCall.run(fn.id, callee);
927
+ }
928
+ }
929
+ // Also add deps from the function's own file-level dependencies if we can extract them
930
+ // (dependencies are file-level, stored in function row context)
931
+ }
932
+ // Also rebuild dependency edges from the functions' import data
933
+ const insertFnDep = this.db.prepare('INSERT OR IGNORE INTO function_dependencies (function_id, dependency) VALUES (?, ?)');
934
+ for (const fn of allFns) {
935
+ const meta = tryParseJson(fn.metadata_json) ?? {};
936
+ const usedImports = meta.usedImports ?? [];
937
+ for (const imp of usedImports) {
938
+ insertFnDep.run(fn.id, imp);
939
+ }
940
+ }
941
+ });
942
+ txn();
943
+ }
944
+ async getTransitiveDependencies(functionName, maxDepth = 10) {
945
+ this.ensureInitialized();
946
+ const result = [];
947
+ const visited = new Set();
948
+ // Find starting function(s)
949
+ const startFns = this.db.prepare('SELECT id FROM functions WHERE name = ?').all(functionName);
950
+ if (startFns.length === 0)
951
+ return result;
952
+ // Use recursive CTE for transitive closure
953
+ const rows = this.db.prepare(`
954
+ WITH RECURSIVE deps(id, callee_name, depth) AS (
955
+ SELECT fc.caller_id, fc.callee_name, 1
956
+ FROM function_calls fc
957
+ WHERE fc.caller_id IN (SELECT id FROM functions WHERE name = ?)
958
+ UNION
959
+ SELECT fc.caller_id, fc.callee_name, deps.depth + 1
960
+ FROM function_calls fc
961
+ JOIN deps ON fc.caller_id IN (SELECT id FROM functions WHERE name = deps.callee_name)
962
+ WHERE deps.depth < ?
963
+ )
964
+ SELECT DISTINCT callee_name as name, depth FROM deps ORDER BY depth, name
965
+ `).all(functionName, maxDepth);
966
+ return rows;
967
+ }
968
+ async getTransitiveCallers(functionName, maxDepth = 10) {
969
+ this.ensureInitialized();
970
+ const rows = this.db.prepare(`
971
+ WITH RECURSIVE callers(id, caller_name, depth) AS (
972
+ SELECT fc.caller_id, f.name, 1
973
+ FROM function_calls fc
974
+ JOIN functions f ON f.id = fc.caller_id
975
+ WHERE fc.callee_name = ?
976
+ UNION
977
+ SELECT fc.caller_id, f2.name, callers.depth + 1
978
+ FROM function_calls fc
979
+ JOIN functions f2 ON f2.id = fc.caller_id
980
+ JOIN functions f3 ON f3.name = callers.caller_name AND fc.callee_name = f3.name
981
+ WHERE callers.depth < ?
982
+ )
983
+ SELECT DISTINCT caller_name as name, depth FROM callers ORDER BY depth, name
984
+ `).all(functionName, maxDepth);
985
+ return rows;
986
+ }
987
+ async detectCircularDependencies() {
988
+ this.ensureInitialized();
989
+ // SQLite recursive CTE for cycle detection
990
+ const rows = this.db.prepare(`
991
+ WITH RECURSIVE paths(start_name, path, current_name, depth) AS (
992
+ SELECT f.name, f.name, fc.callee_name, 1
993
+ FROM functions f
994
+ JOIN function_calls fc ON fc.caller_id = f.id
995
+ UNION
996
+ SELECT paths.start_name,
997
+ paths.path || '→' || fc.callee_name,
998
+ fc.callee_name,
999
+ paths.depth + 1
1000
+ FROM paths
1001
+ JOIN functions f ON f.name = paths.current_name
1002
+ JOIN function_calls fc ON fc.caller_id = f.id
1003
+ WHERE paths.depth < 20
1004
+ AND instr(paths.path, fc.callee_name) = 0
1005
+ )
1006
+ SELECT DISTINCT path FROM paths
1007
+ WHERE current_name = start_name AND depth > 1
1008
+ `).all();
1009
+ return rows.map(r => r.path.split('→'));
1010
+ }
1011
+ async calculateDependencyDepths() {
1012
+ this.ensureInitialized();
1013
+ const functions = this.db.prepare('SELECT id, name FROM functions').all();
1014
+ const update = this.db.prepare('UPDATE functions SET dependency_depth = ? WHERE id = ?');
1015
+ // Calculate depths outside transaction since getTransitiveDependencies is async
1016
+ const depths = [];
1017
+ for (const fn of functions) {
1018
+ const deps = await this.getTransitiveDependencies(fn.name);
1019
+ const maxDepth = deps.length > 0 ? Math.max(...deps.map(d => d.depth)) : 0;
1020
+ depths.push({ id: fn.id, maxDepth });
1021
+ }
1022
+ const txn = this.db.transaction(() => {
1023
+ for (const { id, maxDepth } of depths) {
1024
+ update.run(maxDepth, id);
1025
+ }
1026
+ });
1027
+ txn();
1028
+ }
1029
+ // ── Search ──────────────────────────────────────────────────────────
1030
+ async searchFunctions(options) {
1031
+ this.ensureInitialized();
1032
+ const startTime = Date.now();
1033
+ const queryParser = new QueryParser();
1034
+ let parsedQuery;
1035
+ if (options.query) {
1036
+ parsedQuery = options.parsedQuery || queryParser.parse(options.query);
1037
+ }
1038
+ else if (options.parsedQuery) {
1039
+ parsedQuery = options.parsedQuery;
1040
+ }
1041
+ // If there's a parsed query, use the SQL path (handles both FTS5 and operator-only queries)
1042
+ if (parsedQuery) {
1043
+ const compiled = compileToSQL(parsedQuery, { defaultLimit: options.limit || 50, offset: options.offset || 0 });
1044
+ return this.executeCompiledSearch(compiled, parsedQuery, options, startTime);
1045
+ }
1046
+ // Otherwise, get all functions and apply filters in-memory (backward compatible)
1047
+ const rows = this.db.prepare('SELECT *, rowid as "$loki" FROM functions').all();
1048
+ let results = rows.map((r) => this.rowToFunctionDoc(r));
1049
+ // Apply filters (parsedQuery is undefined here — the if-parsedQuery branch returned early)
1050
+ const combinedFilters = this.mergeFilters(options.filters, undefined);
1051
+ results = this.applyFilters(results, combinedFilters);
1052
+ const totalCount = results.length;
1053
+ const limit = options.limit || 50;
1054
+ const offset = options.offset || 0;
1055
+ results = results.slice(offset, offset + limit);
1056
+ const functions = results.map(doc => ({
1057
+ ...doc,
1058
+ score: 50
1059
+ }));
1060
+ return {
1061
+ functions,
1062
+ totalCount,
1063
+ query: options.query,
1064
+ parsedQuery,
1065
+ executionTime: Date.now() - startTime
1066
+ };
1067
+ }
1068
+ executeCompiledSearch(compiled, parsedQuery, options, startTime) {
1069
+ let sql;
1070
+ const params = { ...compiled.params };
1071
+ let afterFrom = '';
1072
+ // Collect JOINs first — they must precede WHERE in SQL
1073
+ for (const join of compiled.joinClauses) {
1074
+ afterFrom += ` ${join}`;
1075
+ }
1076
+ if (compiled.ftsMatch) {
1077
+ // FTS5 path
1078
+ sql = `SELECT f.*, f.rowid as "$loki", bm25(functions_fts) as score
1079
+ FROM functions f
1080
+ JOIN functions_fts ON functions_fts.rowid = f.id${afterFrom}
1081
+ WHERE functions_fts MATCH @_ftsMatch`;
1082
+ params['_ftsMatch'] = compiled.ftsMatch;
1083
+ }
1084
+ else {
1085
+ sql = `SELECT f.*, f.rowid as "$loki", 0 as score FROM functions f${afterFrom} WHERE 1=1`;
1086
+ }
1087
+ // Add WHERE clauses
1088
+ for (const clause of compiled.whereClauses) {
1089
+ sql += ` AND (${clause})`;
1090
+ }
1091
+ // Ordering
1092
+ sql += ` ORDER BY ${compiled.orderBy}`;
1093
+ // Limit/offset
1094
+ sql += ` LIMIT ${compiled.limit} OFFSET ${compiled.offset}`;
1095
+ const rows = this.db.prepare(sql).all(params);
1096
+ // Get total count (without limit)
1097
+ let totalCount = rows.length;
1098
+ try {
1099
+ let countSql;
1100
+ let countAfterFrom = '';
1101
+ for (const join of compiled.joinClauses) {
1102
+ countAfterFrom += ` ${join}`;
1103
+ }
1104
+ if (compiled.ftsMatch) {
1105
+ countSql = `SELECT COUNT(*) as cnt FROM functions f
1106
+ JOIN functions_fts ON functions_fts.rowid = f.id${countAfterFrom}
1107
+ WHERE functions_fts MATCH @_ftsMatch`;
1108
+ }
1109
+ else {
1110
+ countSql = `SELECT COUNT(*) as cnt FROM functions f${countAfterFrom} WHERE 1=1`;
1111
+ }
1112
+ for (const clause of compiled.whereClauses) {
1113
+ countSql += ` AND (${clause})`;
1114
+ }
1115
+ const countParams = { ...params };
1116
+ delete countParams['_ftsMatch'];
1117
+ if (compiled.ftsMatch) {
1118
+ countParams['_ftsMatch'] = compiled.ftsMatch;
1119
+ }
1120
+ const countResult = this.db.prepare(countSql).get(countParams);
1121
+ totalCount = countResult?.cnt ?? rows.length;
1122
+ }
1123
+ catch {
1124
+ // Fall back to results length
1125
+ }
1126
+ // Convert rows to FunctionDocument, applying excluded terms
1127
+ let docs = rows.map((r) => this.rowToFunctionDoc(r));
1128
+ if (parsedQuery.excludedTerms.length > 0) {
1129
+ docs = this.excludeTermsFromResults(docs, parsedQuery.excludedTerms);
1130
+ }
1131
+ // Apply in-memory filters (for filters not handled by compileToSQL)
1132
+ const combinedFilters = this.mergeFilters(options.filters, parsedQuery.filters);
1133
+ docs = this.applyFilters(docs, combinedFilters);
1134
+ const functions = docs.map(doc => ({
1135
+ ...doc,
1136
+ score: doc.score ?? 0
1137
+ }));
1138
+ return {
1139
+ functions,
1140
+ totalCount,
1141
+ query: options.query,
1142
+ parsedQuery,
1143
+ executionTime: Date.now() - startTime
1144
+ };
1145
+ }
1146
+ rowToFunctionDoc(row) {
1147
+ const func = this.rowToFunction(row);
1148
+ const doc = {
1149
+ ...func,
1150
+ $loki: row['$loki'] ?? row.id,
1151
+ dependencies: [],
1152
+ };
1153
+ // Populate dependencies from metadata
1154
+ const meta = tryParseJson(row.metadata_json);
1155
+ if (meta?.usedImports) {
1156
+ doc.dependencies = meta.usedImports;
1157
+ }
1158
+ return doc;
1159
+ }
1160
+ async findDefinition(name, filePath) {
1161
+ this.ensureInitialized();
1162
+ let row;
1163
+ if (filePath) {
1164
+ row = this.db.prepare('SELECT * FROM functions WHERE name = ? AND file_path = ?').get(name, filePath);
1165
+ }
1166
+ else {
1167
+ row = this.db.prepare('SELECT * FROM functions WHERE name = ?').get(name);
1168
+ }
1169
+ if (!row)
1170
+ return null;
1171
+ const func = this.rowToFunction(row);
1172
+ return func;
1173
+ }
1174
+ // ── Stats ───────────────────────────────────────────────────────────
1175
+ async getAllFunctions() {
1176
+ this.ensureInitialized();
1177
+ const rows = this.db.prepare('SELECT * FROM functions').all();
1178
+ return rows.map((r) => this.rowToFunction(r));
1179
+ }
1180
+ async getStats() {
1181
+ this.ensureInitialized();
1182
+ const totalFunctions = this.db.prepare('SELECT COUNT(*) as cnt FROM functions').get().cnt;
1183
+ const languages = {};
1184
+ const langRows = this.db.prepare('SELECT language, COUNT(*) as cnt FROM functions GROUP BY language').all();
1185
+ for (const r of langRows) {
1186
+ languages[r.language] = r.cnt;
1187
+ }
1188
+ const depRows = this.db.prepare('SELECT dependency as name, COUNT(*) as cnt FROM function_dependencies GROUP BY dependency ORDER BY cnt DESC LIMIT 10').all();
1189
+ const topDependencies = depRows.map((r) => ({ name: r.name, count: r.cnt }));
1190
+ const filesIndexed = this.db.prepare('SELECT COUNT(DISTINCT file_path) as cnt FROM functions').get().cnt;
1191
+ return {
1192
+ totalFunctions,
1193
+ languages,
1194
+ topDependencies,
1195
+ filesIndexed,
1196
+ lastUpdated: new Date()
1197
+ };
1198
+ }
1199
+ // ── Lifecycle: clear & close ────────────────────────────────────────
1200
+ async clearIndex() {
1201
+ this.ensureInitialized();
1202
+ this.db.transaction(() => {
1203
+ this.db.prepare('DELETE FROM functions').run();
1204
+ // FTS5 triggers handle cleanup
1205
+ this.db.prepare('DELETE FROM audit_results').run();
1206
+ this.db.prepare('DELETE FROM code_maps').run();
1207
+ this.db.prepare('DELETE FROM schema_definitions').run();
1208
+ this.db.prepare('DELETE FROM schema_usage').run();
1209
+ // Preserve: project_tasks, analyzer_configs, whitelist
1210
+ })();
1211
+ }
1212
+ async close() {
1213
+ if (this.isInitialized) {
1214
+ this.taskRepository = null;
1215
+ this.stmts.clear();
1216
+ this.db.close();
1217
+ this.isInitialized = false;
1218
+ }
1219
+ }
1220
+ // ── File sync & bulk cleanup ────────────────────────────────────────
1221
+ async synchronizeFile(filePath) {
1222
+ this.ensureInitialized();
1223
+ try {
1224
+ await fs.access(filePath);
1225
+ }
1226
+ catch {
1227
+ // File doesn't exist — remove all functions for it
1228
+ const result = this.db.prepare('DELETE FROM functions WHERE file_path = ?').run(filePath);
1229
+ return { added: 0, updated: 0, removed: result.changes };
1230
+ }
1231
+ try {
1232
+ const { FunctionScanner } = await import('./functionScanner.js');
1233
+ const scanner = new FunctionScanner();
1234
+ const fileContent = await fs.readFile(filePath, 'utf-8');
1235
+ const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'javascript';
1236
+ const parsedFunctions = await scanner.scanFunctions(fileContent, filePath, language);
1237
+ return await this.syncFileIndex(filePath, parsedFunctions);
1238
+ }
1239
+ catch (error) {
1240
+ throw new Error(`Failed to sync file: ${error instanceof Error ? error.message : 'Unknown error'}`);
1241
+ }
1242
+ }
1243
+ async bulkCleanup() {
1244
+ this.ensureInitialized();
1245
+ const files = this.db.prepare('SELECT DISTINCT file_path FROM functions').all();
1246
+ const removedFiles = [];
1247
+ const errors = [];
1248
+ let removedCount = 0;
1249
+ let scannedCount = 0;
1250
+ for (const { file_path: fp } of files) {
1251
+ scannedCount++;
1252
+ try {
1253
+ await fs.access(fp);
1254
+ }
1255
+ catch {
1256
+ const result = this.db.prepare('DELETE FROM functions WHERE file_path = ?').run(fp);
1257
+ removedCount += result.changes;
1258
+ removedFiles.push(fp);
1259
+ }
1260
+ }
1261
+ return { scannedCount, removedCount, removedFiles, errors };
1262
+ }
1263
+ async deepSync(progressCallback) {
1264
+ this.ensureInitialized();
1265
+ const files = this.db.prepare('SELECT DISTINCT file_path FROM functions').all();
1266
+ let syncedFiles = 0;
1267
+ let totalAdded = 0;
1268
+ let totalUpdated = 0;
1269
+ let totalRemoved = 0;
1270
+ const errors = [];
1271
+ let current = 0;
1272
+ for (const { file_path: fp } of files) {
1273
+ current++;
1274
+ if (progressCallback) {
1275
+ progressCallback({ current, total: files.length, file: fp });
1276
+ }
1277
+ try {
1278
+ const result = await this.synchronizeFile(fp);
1279
+ if (result) {
1280
+ syncedFiles++;
1281
+ totalAdded += result.added;
1282
+ totalUpdated += result.updated;
1283
+ totalRemoved += result.removed;
1284
+ }
1285
+ }
1286
+ catch (error) {
1287
+ errors.push({
1288
+ file: fp,
1289
+ error: error instanceof Error ? error.message : String(error)
1290
+ });
1291
+ }
1292
+ }
1293
+ return {
1294
+ syncedFiles,
1295
+ addedFunctions: totalAdded,
1296
+ updatedFunctions: totalUpdated,
1297
+ removedFunctions: totalRemoved,
1298
+ errors
1299
+ };
1300
+ }
1301
+ // ── Diff-scoped audit detection (Spec 04) ────────────────────────────
1302
+ /**
1303
+ * For a set of file paths, re-parse each file and return only functions
1304
+ * whose content_hash differs from the stored value (plus new functions).
1305
+ * Deleted functions are removed from the index.
1306
+ *
1307
+ * Returns the list of changed/new function metadata for scoped analysis,
1308
+ * and the set of file paths that were actually touched.
1309
+ */
1310
+ async detectChangedFunctions(filePaths) {
1311
+ this.ensureInitialized();
1312
+ const changedFunctions = [];
1313
+ const deletedFunctions = [];
1314
+ const changedFilePaths = [];
1315
+ const errors = [];
1316
+ const { FunctionScanner } = await import('./functionScanner.js');
1317
+ for (const filePath of filePaths) {
1318
+ try {
1319
+ await fs.access(filePath);
1320
+ }
1321
+ catch {
1322
+ // File doesn't exist — remove all its functions
1323
+ const removed = this.db.prepare('SELECT * FROM functions WHERE file_path = ?').all(filePath);
1324
+ if (removed.length > 0) {
1325
+ const parsed = removed.map((r) => this.rowToFunction(r));
1326
+ deletedFunctions.push(...parsed);
1327
+ this.db.prepare('DELETE FROM functions WHERE file_path = ?').run(filePath);
1328
+ changedFilePaths.push(filePath);
1329
+ }
1330
+ continue;
1331
+ }
1332
+ try {
1333
+ const scanner = new FunctionScanner();
1334
+ const fileContent = await fs.readFile(filePath, 'utf-8');
1335
+ const language = (filePath.endsWith('.ts') || filePath.endsWith('.tsx'))
1336
+ ? 'typescript' : 'javascript';
1337
+ const currentFunctions = await scanner.scanFunctions(fileContent, filePath, language);
1338
+ // Get existing functions for this file from DB
1339
+ const existing = this.db.prepare('SELECT * FROM functions WHERE file_path = ?').all(filePath);
1340
+ // Build a name → existing-row map
1341
+ const existingByName = new Map();
1342
+ for (const e of existing) {
1343
+ existingByName.set(e.name, e);
1344
+ }
1345
+ const currentNames = new Set(currentFunctions.map((f) => f.name));
1346
+ let fileChanged = false;
1347
+ for (const func of currentFunctions) {
1348
+ const existingRow = existingByName.get(func.name);
1349
+ // Convert scanner output to EnhancedFunctionMetadata shape
1350
+ const funcMeta = {
1351
+ ...func,
1352
+ complexity: func.complexity,
1353
+ content_hash: computeContentHash(func.body ?? func.metadata?.body, func.signature),
1354
+ };
1355
+ const newHash = funcMeta.content_hash;
1356
+ if (!existingRow) {
1357
+ // New function
1358
+ changedFunctions.push(funcMeta);
1359
+ fileChanged = true;
1360
+ }
1361
+ else if (existingRow.content_hash !== newHash) {
1362
+ // Changed function
1363
+ changedFunctions.push(funcMeta);
1364
+ fileChanged = true;
1365
+ }
1366
+ }
1367
+ // Detect deleted functions
1368
+ for (const e of existing) {
1369
+ if (!currentNames.has(e.name)) {
1370
+ deletedFunctions.push(this.rowToFunction(e));
1371
+ fileChanged = true;
1372
+ }
1373
+ }
1374
+ if (fileChanged) {
1375
+ changedFilePaths.push(filePath);
1376
+ }
1377
+ // Sync the file into the index (upsert + deletes)
1378
+ await this.syncFileIndex(filePath, currentFunctions);
1379
+ }
1380
+ catch (error) {
1381
+ errors.push({
1382
+ file: filePath,
1383
+ error: error instanceof Error ? error.message : String(error)
1384
+ });
1385
+ }
1386
+ }
1387
+ return { changedFunctions, deletedFunctions, changedFilePaths, errors };
1388
+ }
1389
+ /**
1390
+ * Given a project root, detect all indexed files whose mtime is newer than
1391
+ * the stored last_modified timestamp. Returns file paths for further processing.
1392
+ */
1393
+ async detectModifiedFiles(projectRoot) {
1394
+ this.ensureInitialized();
1395
+ const files = this.db.prepare('SELECT DISTINCT file_path, last_modified FROM functions').all();
1396
+ const modifiedFiles = [];
1397
+ const uniqueFiles = new Map();
1398
+ for (const row of files) {
1399
+ if (!uniqueFiles.has(row.file_path)) {
1400
+ uniqueFiles.set(row.file_path, row.last_modified);
1401
+ }
1402
+ }
1403
+ for (const [filePath, storedMtime] of uniqueFiles) {
1404
+ try {
1405
+ const stat = await fs.stat(filePath);
1406
+ // Normalize to ISO string for comparison
1407
+ const currentMtime = stat.mtime.toISOString();
1408
+ if (!storedMtime || currentMtime > storedMtime) {
1409
+ modifiedFiles.push(filePath);
1410
+ }
1411
+ }
1412
+ catch {
1413
+ // File doesn't exist — it will be handled as a deletion
1414
+ modifiedFiles.push(filePath);
1415
+ }
1416
+ }
1417
+ return modifiedFiles;
1418
+ }
1419
+ /**
1420
+ * Get content hashes for a set of file paths. Returns a map:
1421
+ * file_path → { name → content_hash }
1422
+ */
1423
+ getContentHashesForFiles(filePaths) {
1424
+ this.ensureInitialized();
1425
+ const result = new Map();
1426
+ if (filePaths.length === 0)
1427
+ return result;
1428
+ const placeholders = filePaths.map(() => '?').join(', ');
1429
+ const rows = this.db.prepare(`SELECT file_path, name, content_hash FROM functions WHERE file_path IN (${placeholders})`).all(...filePaths);
1430
+ for (const row of rows) {
1431
+ if (!result.has(row.file_path)) {
1432
+ result.set(row.file_path, new Map());
1433
+ }
1434
+ result.get(row.file_path).set(row.name, row.content_hash ?? '');
1435
+ }
1436
+ return result;
1437
+ }
1438
+ // ── Legacy search helpers (for in-memory filtering) ─────────────────
1439
+ mergeFilters(optionsFilters, queryFilters) {
1440
+ const merged = {};
1441
+ if (optionsFilters)
1442
+ Object.assign(merged, optionsFilters);
1443
+ if (queryFilters) {
1444
+ if (queryFilters.language)
1445
+ merged.language = queryFilters.language;
1446
+ if (queryFilters.filePath)
1447
+ merged.filePath = queryFilters.filePath;
1448
+ if (queryFilters.fileType)
1449
+ merged.fileType = queryFilters.fileType;
1450
+ if (queryFilters.hasJsDoc !== undefined)
1451
+ merged.hasJsDoc = queryFilters.hasJsDoc;
1452
+ if (queryFilters.complexity)
1453
+ merged.complexity = queryFilters.complexity;
1454
+ if (queryFilters.dateRange)
1455
+ merged.dateRange = queryFilters.dateRange;
1456
+ if (queryFilters.metadata)
1457
+ merged.metadata = queryFilters.metadata;
1458
+ }
1459
+ return merged;
1460
+ }
1461
+ applyFilters(results, filters) {
1462
+ if (!filters)
1463
+ return results;
1464
+ let filtered = results;
1465
+ if (filters.language) {
1466
+ filtered = filtered.filter(doc => doc.language === filters.language);
1467
+ }
1468
+ if (filters.filePath) {
1469
+ if (filters.filePath.includes('*') || filters.filePath.includes('?')) {
1470
+ const pattern = filters.filePath.replace(/\*/g, '.*').replace(/\?/g, '.').replace(/\//g, '\\/');
1471
+ const regex = new RegExp(pattern);
1472
+ filtered = filtered.filter(doc => regex.test(doc.filePath));
1473
+ }
1474
+ else if (filters.filePath.endsWith('.ts') || filters.filePath.endsWith('.tsx') ||
1475
+ filters.filePath.endsWith('.js') || filters.filePath.endsWith('.jsx')) {
1476
+ filtered = filtered.filter(doc => doc.filePath.endsWith(filters.filePath));
1477
+ }
1478
+ else {
1479
+ filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
1480
+ }
1481
+ }
1482
+ if (filters.fileType) {
1483
+ filtered = filtered.filter(doc => doc.filePath.endsWith(filters.fileType));
1484
+ }
1485
+ if (filters.hasJsDoc !== undefined) {
1486
+ filtered = filtered.filter(doc => {
1487
+ const hasJsDoc = doc.jsDoc && doc.jsDoc.description && doc.jsDoc.description.length > 0;
1488
+ return filters.hasJsDoc ? hasJsDoc : !hasJsDoc;
1489
+ });
1490
+ }
1491
+ if (filters.complexity) {
1492
+ filtered = filtered.filter(doc => {
1493
+ if (!doc.complexity)
1494
+ return false;
1495
+ const min = filters.complexity.min || 0;
1496
+ const max = filters.complexity.max || Infinity;
1497
+ return doc.complexity >= min && doc.complexity <= max;
1498
+ });
1499
+ }
1500
+ if (filters.hasAnyDependency && filters.hasAnyDependency.length > 0) {
1501
+ filtered = filtered.filter(doc => filters.hasAnyDependency.some(dep => doc.dependencies.includes(dep)));
1502
+ }
1503
+ if (filters.metadata) {
1504
+ filtered = filtered.filter(doc => {
1505
+ if (!doc.metadata)
1506
+ return false;
1507
+ const m = filters.metadata;
1508
+ if (m.entityType && doc.metadata.entityType !== m.entityType)
1509
+ return false;
1510
+ if (m.componentType && doc.metadata.componentType !== m.componentType)
1511
+ return false;
1512
+ if (m.hasHook) {
1513
+ if (!doc.metadata.hooks)
1514
+ return false;
1515
+ const found = doc.metadata.hooks.some((h) => h.name?.toLowerCase().includes(m.hasHook.toLowerCase()));
1516
+ if (!found)
1517
+ return false;
1518
+ }
1519
+ if (m.hasProp) {
1520
+ if (!doc.metadata.props)
1521
+ return false;
1522
+ const found = doc.metadata.props.some((p) => p.name?.toLowerCase().includes(m.hasProp.toLowerCase()));
1523
+ if (!found)
1524
+ return false;
1525
+ }
1526
+ if (m.usesDependency) {
1527
+ const dep = m.usesDependency.toLowerCase();
1528
+ const inFile = doc.dependencies.some(d => d.toLowerCase().includes(dep));
1529
+ const inFunc = doc.metadata.usedImports?.some(i => i.toLowerCase().includes(dep)) ?? false;
1530
+ if (!inFile && !inFunc)
1531
+ return false;
1532
+ }
1533
+ if (m.callsFunction) {
1534
+ const target = m.callsFunction.toLowerCase();
1535
+ const calls = doc.metadata.functionCalls;
1536
+ if (!calls || !calls.some(c => c.toLowerCase().includes(target)))
1537
+ return false;
1538
+ }
1539
+ if (m.calledByFunction) {
1540
+ const caller = m.calledByFunction.toLowerCase();
1541
+ const calledBy = doc.metadata.calledBy;
1542
+ if (!calledBy || !calledBy.some(c => c.toLowerCase().includes(caller)))
1543
+ return false;
1544
+ }
1545
+ if (m.dependsOnModule) {
1546
+ const mod = m.dependsOnModule.toLowerCase();
1547
+ const inFile2 = doc.filePath.toLowerCase().includes(mod);
1548
+ const inDep = doc.dependencies.some(d => d.toLowerCase().includes(mod));
1549
+ const inCall = doc.metadata.functionCalls?.some(c => c.toLowerCase().includes(mod)) ?? false;
1550
+ if (!inFile2 && !inDep && !inCall)
1551
+ return false;
1552
+ }
1553
+ if (m.hasUnusedImports) {
1554
+ const unused = doc.metadata.unusedImports;
1555
+ if (!unused || unused.length === 0)
1556
+ return false;
1557
+ }
1558
+ return true;
1559
+ });
1560
+ }
1561
+ return filtered;
1562
+ }
1563
+ excludeTermsFromResults(results, excludedTerms) {
1564
+ return results.filter(doc => {
1565
+ const searchText = [
1566
+ doc.name, doc.signature, doc.purpose, doc.context,
1567
+ doc.jsDoc?.description, doc.returnType,
1568
+ ...(doc.parameters || []).map((p) => `${p.name} ${p.description || ''}`),
1569
+ ...doc.dependencies
1570
+ ].filter(Boolean).join(' ').toLowerCase();
1571
+ return !excludedTerms.some(term => searchText.includes(term.toLowerCase()));
1572
+ });
1573
+ }
1574
+ // ── Whitelist ───────────────────────────────────────────────────────
1575
+ async initializeDefaultWhitelists() {
1576
+ const defaults = [
1577
+ { name: 'Date', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1578
+ { name: 'Error', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1579
+ { name: 'Array', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1580
+ { name: 'Map', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1581
+ { name: 'Set', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1582
+ { name: 'Promise', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1583
+ { name: 'RegExp', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'javascript', addedBy: 'system', addedAt: new Date() },
1584
+ { name: 'URL', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1585
+ { name: 'URLSearchParams', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1586
+ { name: 'FormData', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1587
+ { name: 'Headers', type: WhitelistType.PlatformAPI, status: WhitelistStatus.Active, category: 'dom', addedBy: 'system', addedAt: new Date() },
1588
+ { name: 'fs', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['fs', 'node:fs', 'fs/promises'], addedBy: 'system', addedAt: new Date() },
1589
+ { name: 'path', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['path', 'node:path'], addedBy: 'system', addedAt: new Date() },
1590
+ { name: 'crypto', type: WhitelistType.NodeBuiltin, status: WhitelistStatus.Active, patterns: ['crypto', 'node:crypto'], addedBy: 'system', addedAt: new Date() },
1591
+ { name: 'NextResponse', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
1592
+ { name: 'NextRequest', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nextjs', addedBy: 'system', addedAt: new Date() },
1593
+ { name: 'Response', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
1594
+ { name: 'Request', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'web-api', addedBy: 'system', addedAt: new Date() },
1595
+ { name: 'Component', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1596
+ { name: 'PureComponent', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1597
+ { name: 'Fragment', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1598
+ { name: 'StrictMode', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1599
+ { name: 'Suspense', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'react', addedBy: 'system', addedAt: new Date() },
1600
+ { name: 'Pool', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
1601
+ { name: 'Client', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['pg'], addedBy: 'system', addedAt: new Date() },
1602
+ { name: 'MongoClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['mongodb'], addedBy: 'system', addedAt: new Date() },
1603
+ { name: 'PrismaClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'database', patterns: ['@prisma/client'], addedBy: 'system', addedAt: new Date() },
1604
+ { name: 'StackServerApp', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
1605
+ { name: 'StackClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@stackframe/stack'], addedBy: 'system', addedAt: new Date() },
1606
+ { name: 'ClerkProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@clerk/nextjs'], addedBy: 'system', addedAt: new Date() },
1607
+ { name: 'Auth0Provider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'auth', patterns: ['@auth0/nextjs-auth0'], addedBy: 'system', addedAt: new Date() },
1608
+ { name: 'Axios', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', patterns: ['axios'], addedBy: 'system', addedAt: new Date() },
1609
+ { name: 'HttpClient', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'http', addedBy: 'system', addedAt: new Date() },
1610
+ { name: 'Router', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
1611
+ { name: 'Application', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'express', patterns: ['express'], addedBy: 'system', addedAt: new Date() },
1612
+ { name: 'TestingModule', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@nestjs/testing'], addedBy: 'system', addedAt: new Date() },
1613
+ { name: 'MockedProvider', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'testing', patterns: ['@apollo/client/testing'], addedBy: 'system', addedAt: new Date() },
1614
+ { name: 'EventEmitter', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['events', 'node:events'], addedBy: 'system', addedAt: new Date() },
1615
+ { name: 'Readable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
1616
+ { name: 'Writable', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
1617
+ { name: 'Transform', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', patterns: ['stream', 'node:stream'], addedBy: 'system', addedAt: new Date() },
1618
+ { name: 'Buffer', type: WhitelistType.FrameworkClass, status: WhitelistStatus.Active, category: 'nodejs', addedBy: 'system', addedAt: new Date() },
1619
+ ];
1620
+ const insert = this.db.prepare(`INSERT INTO whitelist (name, type, status, category, description, patterns, added_by, added_at, metadata_json)
1621
+ VALUES (@name, @type, @status, @category, @description, @patterns, @added_by, @added_at, @metadata_json)`);
1622
+ for (const entry of defaults) {
1623
+ insert.run({
1624
+ name: entry.name,
1625
+ type: entry.type,
1626
+ status: entry.status,
1627
+ category: entry.category ?? null,
1628
+ description: entry.description ?? null,
1629
+ patterns: JSON.stringify(entry.patterns ?? []),
1630
+ added_by: entry.addedBy ?? 'system',
1631
+ added_at: (entry.addedAt ?? new Date()).toISOString(),
1632
+ metadata_json: '{}',
1633
+ });
1634
+ }
1635
+ }
1636
+ async getWhitelist(type, status) {
1637
+ this.ensureInitialized();
1638
+ const rows = this.whitelistAdapter.find({
1639
+ ...(type ? { type } : {}),
1640
+ ...(status ? { status } : {}),
1641
+ });
1642
+ return rows.map((r) => ({
1643
+ name: r.name,
1644
+ type: r.type,
1645
+ status: r.status,
1646
+ category: r.category,
1647
+ description: r.description,
1648
+ patterns: tryParseJson(r.patterns),
1649
+ addedBy: r.added_by ?? r.addedBy,
1650
+ addedAt: new Date(r.added_at ?? r.addedAt),
1651
+ updatedAt: r.updated_at ? new Date(r.updated_at) : undefined,
1652
+ metadata: tryParseJson(r.metadata_json),
1653
+ }));
1654
+ }
1655
+ async addWhitelistEntry(entry) {
1656
+ this.ensureInitialized();
1657
+ const row = this.whitelistAdapter.insert({
1658
+ name: entry.name,
1659
+ type: entry.type,
1660
+ status: entry.status ?? WhitelistStatus.Active,
1661
+ category: entry.category ?? null,
1662
+ description: entry.description ?? null,
1663
+ patterns: JSON.stringify(entry.patterns ?? []),
1664
+ added_by: entry.addedBy ?? 'user',
1665
+ added_at: new Date().toISOString(),
1666
+ metadata_json: '{}',
1667
+ });
1668
+ return {
1669
+ name: row.name,
1670
+ type: row.type,
1671
+ status: row.status,
1672
+ category: row.category,
1673
+ description: row.description,
1674
+ patterns: tryParseJson(row.patterns),
1675
+ addedBy: row.added_by,
1676
+ addedAt: new Date(row.added_at),
1677
+ };
1678
+ }
1679
+ async updateWhitelistStatus(name, status) {
1680
+ this.ensureInitialized();
1681
+ this.db.prepare('UPDATE whitelist SET status = ?, updated_at = ? WHERE name = ?')
1682
+ .run(status, new Date().toISOString(), name);
1683
+ }
1684
+ isWhitelisted(name, type) {
1685
+ if (!this.isInitialized)
1686
+ return false;
1687
+ const rows = this.db.prepare('SELECT name, patterns FROM whitelist WHERE type = ? AND status = ?').all(type, WhitelistStatus.Active);
1688
+ return rows.some(entry => {
1689
+ if (entry.name === name)
1690
+ return true;
1691
+ const patterns = tryParseJson(entry.patterns);
1692
+ if (patterns && Array.isArray(patterns)) {
1693
+ return patterns.some((p) => {
1694
+ if (p.includes('*')) {
1695
+ const regex = new RegExp(p.replace(/\*/g, '.*'));
1696
+ return regex.test(name);
1697
+ }
1698
+ return p === name;
1699
+ });
1700
+ }
1701
+ return false;
1702
+ });
1703
+ }
1704
+ async detectWhitelistCandidates() {
1705
+ return [];
1706
+ }
1707
+ // ── Audit results ───────────────────────────────────────────────────
1708
+ async storeAuditResults(auditResult, projectPath) {
1709
+ this.ensureInitialized();
1710
+ const auditId = `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1711
+ this.db.prepare(`INSERT INTO audit_results (audit_id, timestamp, project_path, summary_json, analyzer_results_json, violations_json, recommendations_json, metadata_json, expires_at)
1712
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(auditId, new Date().toISOString(), projectPath, JSON.stringify(auditResult.summary ?? {}), JSON.stringify(auditResult.analyzerResults ?? auditResult.results ?? {}), JSON.stringify(auditResult.violations ?? null), JSON.stringify(auditResult.recommendations ?? null), JSON.stringify(auditResult.metadata ?? {}), new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString());
1713
+ this.cleanupExpiredAudits();
1714
+ return auditId;
1715
+ }
1716
+ async getAuditResults(auditId) {
1717
+ this.ensureInitialized();
1718
+ const row = this.db.prepare('SELECT * FROM audit_results WHERE audit_id = ?').get(auditId);
1719
+ if (!row)
1720
+ return null;
1721
+ if (new Date(row.expires_at) <= new Date()) {
1722
+ this.db.prepare('DELETE FROM audit_results WHERE audit_id = ?').run(auditId);
1723
+ return null;
1724
+ }
1725
+ return {
1726
+ auditId: row.audit_id,
1727
+ timestamp: new Date(row.timestamp),
1728
+ projectPath: row.project_path,
1729
+ summary: tryParseJson(row.summary_json),
1730
+ analyzerResults: tryParseJson(row.analyzer_results_json),
1731
+ violations: tryParseJson(row.violations_json),
1732
+ recommendations: tryParseJson(row.recommendations_json),
1733
+ metadata: tryParseJson(row.metadata_json),
1734
+ expiresAt: new Date(row.expires_at),
1735
+ };
1736
+ }
1737
+ async getMostRecentAuditResults(projectPath, resultScope) {
1738
+ this.ensureInitialized();
1739
+ const now = new Date().toISOString();
1740
+ let row;
1741
+ const scopeFilter = resultScope
1742
+ ? "AND json_extract(metadata_json, '$.scope') = ?"
1743
+ : '';
1744
+ if (projectPath) {
1745
+ const params = [projectPath, now];
1746
+ if (resultScope)
1747
+ params.push(resultScope);
1748
+ row = this.db.prepare(`SELECT * FROM audit_results WHERE project_path = ? AND expires_at > ? ${scopeFilter} ORDER BY timestamp DESC, rowid DESC LIMIT 1`).get(...params);
1749
+ }
1750
+ else {
1751
+ const params = [now];
1752
+ if (resultScope)
1753
+ params.push(resultScope);
1754
+ row = this.db.prepare(`SELECT * FROM audit_results WHERE expires_at > ? ${scopeFilter} ORDER BY timestamp DESC, rowid DESC LIMIT 1`).get(...params);
1755
+ }
1756
+ if (!row)
1757
+ return null;
1758
+ return {
1759
+ auditId: row.audit_id,
1760
+ timestamp: new Date(row.timestamp),
1761
+ projectPath: row.project_path,
1762
+ summary: tryParseJson(row.summary_json),
1763
+ analyzerResults: tryParseJson(row.analyzer_results_json),
1764
+ violations: tryParseJson(row.violations_json),
1765
+ recommendations: tryParseJson(row.recommendations_json),
1766
+ metadata: tryParseJson(row.metadata_json),
1767
+ expiresAt: new Date(row.expires_at),
1768
+ };
1769
+ }
1770
+ hasOpenTaskByFingerprint(fingerprint) {
1771
+ if (!fingerprint)
1772
+ return false;
1773
+ return this.getTaskRepository().findOpenByFingerprint(fingerprint).length > 0;
1774
+ }
1775
+ cleanupExpiredAudits() {
1776
+ this.db.prepare('DELETE FROM audit_results WHERE expires_at < ?').run(new Date().toISOString());
1777
+ }
1778
+ // ── Analyzer configs ────────────────────────────────────────────────
1779
+ async storeAnalyzerConfig(analyzerName, config, options) {
1780
+ this.ensureInitialized();
1781
+ const projectPath = options?.projectPath ?? null;
1782
+ const isGlobal = options?.isGlobal ?? true;
1783
+ const existing = this.db.prepare('SELECT id FROM analyzer_configs WHERE analyzer_name = ? AND COALESCE(project_path, \'__global__\') = ? AND is_global = ?').get(analyzerName, projectPath ?? '__global__', isGlobal ? 1 : 0);
1784
+ if (existing) {
1785
+ this.db.prepare('UPDATE analyzer_configs SET config_json = ?, updated_at = ?, metadata_json = ? WHERE id = ?').run(JSON.stringify(config), new Date().toISOString(), JSON.stringify(options?.metadata ?? {}), existing.id);
1786
+ }
1787
+ else {
1788
+ this.db.prepare(`INSERT INTO analyzer_configs (analyzer_name, project_path, is_global, config_json, created_by, created_at, updated_at, metadata_json)
1789
+ VALUES (?, ?, ?, ?, 'user', ?, ?, ?)`).run(analyzerName, projectPath, isGlobal ? 1 : 0, JSON.stringify(config), new Date().toISOString(), new Date().toISOString(), JSON.stringify(options?.metadata ?? {}));
1790
+ }
1791
+ return analyzerName;
1792
+ }
1793
+ async getAnalyzerConfig(analyzerName, projectPath) {
1794
+ this.ensureInitialized();
1795
+ if (projectPath) {
1796
+ const row = this.db.prepare('SELECT config_json FROM analyzer_configs WHERE analyzer_name = ? AND project_path = ? AND is_global = 0').get(analyzerName, projectPath);
1797
+ if (row)
1798
+ return tryParseJson(row.config_json);
1799
+ }
1800
+ const globalRow = this.db.prepare('SELECT config_json FROM analyzer_configs WHERE analyzer_name = ? AND is_global = 1').get(analyzerName);
1801
+ return globalRow ? tryParseJson(globalRow.config_json) : null;
1802
+ }
1803
+ async getAllAnalyzerConfigs(projectPath) {
1804
+ this.ensureInitialized();
1805
+ const configs = {};
1806
+ const globals = this.db.prepare('SELECT analyzer_name, config_json FROM analyzer_configs WHERE is_global = 1').all();
1807
+ for (const c of globals) {
1808
+ configs[c.analyzer_name] = tryParseJson(c.config_json);
1809
+ }
1810
+ if (projectPath) {
1811
+ const locals = this.db.prepare('SELECT analyzer_name, config_json FROM analyzer_configs WHERE project_path = ? AND is_global = 0').all(projectPath);
1812
+ for (const c of locals) {
1813
+ configs[c.analyzer_name] = tryParseJson(c.config_json);
1814
+ }
1815
+ }
1816
+ return configs;
1817
+ }
1818
+ async deleteAnalyzerConfig(analyzerName, options) {
1819
+ this.ensureInitialized();
1820
+ const result = this.db.prepare('DELETE FROM analyzer_configs WHERE analyzer_name = ? AND COALESCE(project_path, \'__global__\') = ? AND is_global = ?').run(analyzerName, options?.projectPath ?? '__global__', (options?.isGlobal ?? !options?.projectPath) ? 1 : 0);
1821
+ return result.changes > 0;
1822
+ }
1823
+ async resetAnalyzerConfigs(projectPath) {
1824
+ this.ensureInitialized();
1825
+ if (projectPath) {
1826
+ this.db.prepare('DELETE FROM analyzer_configs WHERE project_path = ? AND is_global = 0').run(projectPath);
1827
+ }
1828
+ else {
1829
+ this.db.prepare('DELETE FROM analyzer_configs').run();
1830
+ }
1831
+ }
1832
+ // ── Code maps ───────────────────────────────────────────────────────
1833
+ async storeCodeMapSection(mapId, sectionType, content, metadata) {
1834
+ this.ensureInitialized();
1835
+ this.db.prepare(`INSERT OR REPLACE INTO code_maps (map_id, section_type, content, metadata_json, timestamp, size)
1836
+ VALUES (?, ?, ?, ?, ?, ?)`).run(mapId, sectionType, content, JSON.stringify(metadata ?? {}), new Date().toISOString(), content.length);
1837
+ }
1838
+ async getCodeMapSection(mapId, sectionType) {
1839
+ this.ensureInitialized();
1840
+ const row = this.db.prepare('SELECT content, metadata_json FROM code_maps WHERE map_id = ? AND section_type = ?')
1841
+ .get(mapId, sectionType);
1842
+ return row ? { content: row.content, metadata: tryParseJson(row.metadata_json) ?? {} } : null;
1843
+ }
1844
+ async listCodeMapSections(mapId) {
1845
+ this.ensureInitialized();
1846
+ const rows = this.db.prepare('SELECT section_type, size, timestamp FROM code_maps WHERE map_id = ?')
1847
+ .all(mapId);
1848
+ return rows.map((r) => ({ sectionType: r.section_type, size: r.size ?? 0, timestamp: new Date(r.timestamp) }));
1849
+ }
1850
+ async clearOldCodeMaps(olderThanHours = 24) {
1851
+ this.ensureInitialized();
1852
+ const cutoff = new Date(Date.now() - olderThanHours * 60 * 60 * 1000).toISOString();
1853
+ const result = this.db.prepare('DELETE FROM code_maps WHERE timestamp < ?').run(cutoff);
1854
+ return result.changes;
1855
+ }
1856
+ async deleteCodeMap(mapId) {
1857
+ this.ensureInitialized();
1858
+ const result = this.db.prepare('DELETE FROM code_maps WHERE map_id = ?').run(mapId);
1859
+ return result.changes;
1860
+ }
1861
+ // ── Schema management ───────────────────────────────────────────────
1862
+ async storeSchema(schema) {
1863
+ this.ensureInitialized();
1864
+ const schemaId = `schema_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1865
+ const metadata = {
1866
+ schemaId,
1867
+ schemaName: schema.name,
1868
+ indexedAt: new Date(),
1869
+ tableCount: schema.databases.reduce((acc, db) => acc + db.tables.length, 0),
1870
+ relationshipCount: schema.databases.reduce((acc, db) => acc + (db.relationships?.length || 0), 0),
1871
+ usagePatterns: [],
1872
+ discoveredPatterns: [],
1873
+ violations: []
1874
+ };
1875
+ this.db.prepare('INSERT INTO schema_definitions (schema_id, schema_name, schema_json, metadata_json, indexed_at) VALUES (?, ?, ?, ?, ?)').run(schemaId, schema.name, JSON.stringify(schema), JSON.stringify(metadata), new Date().toISOString());
1876
+ return schemaId;
1877
+ }
1878
+ async getSchema(schemaId) {
1879
+ this.ensureInitialized();
1880
+ const row = this.db.prepare('SELECT schema_json FROM schema_definitions WHERE schema_id = ?').get(schemaId);
1881
+ return row ? tryParseJson(row.schema_json) : null;
1882
+ }
1883
+ async getAllSchemas() {
1884
+ this.ensureInitialized();
1885
+ const rows = this.db.prepare('SELECT schema_id, schema_json, metadata_json FROM schema_definitions').all();
1886
+ return rows.map((r) => ({
1887
+ schemaId: r.schema_id,
1888
+ metadata: tryParseJson(r.metadata_json) ?? {},
1889
+ schema: tryParseJson(r.schema_json),
1890
+ }));
1891
+ }
1892
+ async deleteSchema(schemaId) {
1893
+ this.ensureInitialized();
1894
+ this.db.prepare('DELETE FROM schema_usage WHERE schema_id = ?').run(schemaId);
1895
+ const result = this.db.prepare('DELETE FROM schema_definitions WHERE schema_id = ?').run(schemaId);
1896
+ return result.changes > 0;
1897
+ }
1898
+ async recordSchemaUsage(usage, schemaId) {
1899
+ this.ensureInitialized();
1900
+ const existing = this.db.prepare('SELECT id FROM schema_usage WHERE table_name = ? AND file_path = ? AND function_name = ? AND line = ?').get(usage.tableName, usage.filePath, usage.functionName, usage.line);
1901
+ if (existing) {
1902
+ this.db.prepare('UPDATE schema_usage SET schema_id = ?, usage_type = ?, "column" = ?, raw_query = ?, parameters = ?, recorded_at = ? WHERE id = ?').run(schemaId ?? 'default', usage.usageType, usage.column ?? null, usage.rawQuery ?? null, JSON.stringify(usage.parameters ?? []), new Date().toISOString(), existing.id);
1903
+ }
1904
+ else {
1905
+ this.db.prepare('INSERT INTO schema_usage (schema_id, table_name, file_path, function_name, usage_type, line, "column", raw_query, parameters, recorded_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(schemaId ?? 'default', usage.tableName, usage.filePath, usage.functionName, usage.usageType, usage.line ?? null, usage.column ?? null, usage.rawQuery ?? null, JSON.stringify(usage.parameters ?? []), new Date().toISOString());
1906
+ }
1907
+ }
1908
+ async getSchemaUsage(options = {}) {
1909
+ this.ensureInitialized();
1910
+ const clauses = [];
1911
+ const params = [];
1912
+ if (options.schemaId) {
1913
+ clauses.push('schema_id = ?');
1914
+ params.push(options.schemaId);
1915
+ }
1916
+ if (options.tableName) {
1917
+ clauses.push('table_name = ?');
1918
+ params.push(options.tableName);
1919
+ }
1920
+ if (options.filePath) {
1921
+ clauses.push('file_path = ?');
1922
+ params.push(options.filePath);
1923
+ }
1924
+ if (options.functionName) {
1925
+ clauses.push('function_name = ?');
1926
+ params.push(options.functionName);
1927
+ }
1928
+ if (options.usageType) {
1929
+ clauses.push('usage_type = ?');
1930
+ params.push(options.usageType);
1931
+ }
1932
+ const sql = 'SELECT * FROM schema_usage' + (clauses.length ? ' WHERE ' + clauses.join(' AND ') : '');
1933
+ const rows = this.db.prepare(sql).all(...params);
1934
+ return rows.map((r) => ({
1935
+ tableName: r.table_name,
1936
+ filePath: r.file_path,
1937
+ functionName: r.function_name,
1938
+ usageType: r.usage_type,
1939
+ line: r.line,
1940
+ column: r.column,
1941
+ rawQuery: r.raw_query,
1942
+ parameters: tryParseJson(r.parameters) ?? [],
1943
+ }));
1944
+ }
1945
+ async findFunctionsUsingTable(tableName) {
1946
+ this.ensureInitialized();
1947
+ const rows = this.db.prepare('SELECT function_name, file_path, usage_type, line FROM schema_usage WHERE table_name = ?').all(tableName);
1948
+ return rows.map((r) => ({
1949
+ functionName: r.function_name,
1950
+ filePath: r.file_path,
1951
+ usageType: r.usage_type,
1952
+ line: r.line,
1953
+ }));
1954
+ }
1955
+ async getSchemaStats() {
1956
+ this.ensureInitialized();
1957
+ const totalSchemas = this.db.prepare('SELECT COUNT(*) as cnt FROM schema_definitions').get().cnt;
1958
+ const totalUsagePatterns = this.db.prepare('SELECT COUNT(*) as cnt FROM schema_usage').get().cnt;
1959
+ const schemas = this.db.prepare('SELECT schema_json FROM schema_definitions').all();
1960
+ const totalTables = schemas.reduce((acc, s) => {
1961
+ const schema = tryParseJson(s.schema_json);
1962
+ return acc + (schema?.databases?.reduce((dbAcc, db) => dbAcc + (db.tables?.length ?? 0), 0) ?? 0);
1963
+ }, 0);
1964
+ const mostUsedTables = this.db.prepare('SELECT table_name as tableName, COUNT(*) as usageCount FROM schema_usage GROUP BY table_name ORDER BY usageCount DESC LIMIT 10').all();
1965
+ const usageByType = {};
1966
+ const typeRows = this.db.prepare('SELECT usage_type, COUNT(*) as cnt FROM schema_usage GROUP BY usage_type').all();
1967
+ for (const r of typeRows) {
1968
+ usageByType[r.usage_type] = r.cnt;
1969
+ }
1970
+ return { totalSchemas, totalTables, totalUsagePatterns, mostUsedTables, usageByType };
1971
+ }
1972
+ async searchWithSchemaContext(query, options = {}) {
1973
+ const searchResult = await this.searchFunctions(options);
1974
+ if (!options.includeSchemaUsage)
1975
+ return searchResult;
1976
+ const enhancedFunctions = await Promise.all(searchResult.functions.map(async (func) => {
1977
+ const schemaUsage = await this.getSchemaUsage({
1978
+ filePath: func.filePath,
1979
+ functionName: func.name
1980
+ });
1981
+ return {
1982
+ ...func,
1983
+ schemaUsage,
1984
+ affectedTables: [...new Set(schemaUsage.map(u => u.tableName))],
1985
+ schemaPatterns: [...new Set(schemaUsage.map(u => u.usageType))]
1986
+ };
1987
+ }));
1988
+ const allSchemaUsage = enhancedFunctions.flatMap(f => f.schemaUsage || []);
1989
+ const schemaContext = [...new Set(allSchemaUsage.map((u) => ({
1990
+ tableName: u.tableName, usageType: u.usageType
1991
+ })))];
1992
+ return { ...searchResult, functions: enhancedFunctions, schemaContext };
1993
+ }
1994
+ // ── Project tasks ───────────────────────────────────────────────────
1995
+ getTaskRepository() {
1996
+ this.ensureInitialized();
1997
+ if (!this.taskRepository) {
1998
+ this.taskRepository = new ProjectTaskRepository(() => this.tasksAdapter, () => { });
1999
+ }
2000
+ return this.taskRepository;
2001
+ }
2002
+ async createProjectTask(input) {
2003
+ return this.getTaskRepository().create(input);
2004
+ }
2005
+ async getProjectTask(taskId) {
2006
+ return this.getTaskRepository().getById(taskId);
2007
+ }
2008
+ async listProjectTasks(projectPath, options) {
2009
+ return this.getTaskRepository().list(projectPath, options);
2010
+ }
2011
+ async listProjectTasksTree(projectPath, options) {
2012
+ return this.getTaskRepository().listTree(projectPath, options);
2013
+ }
2014
+ async listActionableProjectTasks(projectPath, options) {
2015
+ return this.getTaskRepository().listActionable(projectPath, options);
2016
+ }
2017
+ async completeProjectTask(taskId) {
2018
+ return this.getTaskRepository().complete(taskId);
2019
+ }
2020
+ async updateProjectTask(taskId, patch) {
2021
+ return this.getTaskRepository().update(taskId, patch);
2022
+ }
2023
+ async deleteProjectTask(taskId, mode) {
2024
+ return this.getTaskRepository().delete(taskId, mode);
2025
+ }
2026
+ }
2027
+ // ── Helpers ────────────────────────────────────────────────────────────
2028
+ function tryParseJson(val) {
2029
+ if (val === null || val === undefined)
2030
+ return null;
2031
+ if (typeof val === 'object')
2032
+ return val;
2033
+ if (typeof val !== 'string')
2034
+ return val;
2035
+ try {
2036
+ return JSON.parse(val);
2037
+ }
2038
+ catch {
2039
+ return val;
2040
+ }
2041
+ }
2042
+ //# sourceMappingURL=codeIndexDB.js.map