codevault 1.8.4 → 1.8.5

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 (351) hide show
  1. package/README.md +3 -2
  2. package/dist/chunking/token-counter.d.ts.map +1 -1
  3. package/dist/chunking/token-counter.js +16 -10
  4. package/dist/chunking/token-counter.js.map +1 -1
  5. package/dist/cli/commands/index-cmd.d.ts.map +1 -1
  6. package/dist/cli/commands/index-cmd.js +108 -97
  7. package/dist/cli/commands/index-cmd.js.map +1 -1
  8. package/dist/cli/commands/interactive-config.d.ts.map +1 -1
  9. package/dist/cli/commands/interactive-config.js +40 -3
  10. package/dist/cli/commands/interactive-config.js.map +1 -1
  11. package/dist/cli/commands/search-cmd.d.ts.map +1 -1
  12. package/dist/cli/commands/search-cmd.js +11 -7
  13. package/dist/cli/commands/search-cmd.js.map +1 -1
  14. package/dist/cli/commands/search-with-code-cmd.d.ts.map +1 -1
  15. package/dist/cli/commands/search-with-code-cmd.js +3 -1
  16. package/dist/cli/commands/search-with-code-cmd.js.map +1 -1
  17. package/dist/cli/utils.d.ts +56 -0
  18. package/dist/cli/utils.d.ts.map +1 -0
  19. package/dist/cli/utils.js +98 -0
  20. package/dist/cli/utils.js.map +1 -0
  21. package/dist/config/constants.d.ts +4 -0
  22. package/dist/config/constants.d.ts.map +1 -1
  23. package/dist/config/constants.js +2 -0
  24. package/dist/config/constants.js.map +1 -1
  25. package/dist/context/packs.d.ts.map +1 -1
  26. package/dist/context/packs.js +3 -1
  27. package/dist/context/packs.js.map +1 -1
  28. package/dist/core/IndexerEngine.d.ts +2 -0
  29. package/dist/core/IndexerEngine.d.ts.map +1 -1
  30. package/dist/core/IndexerEngine.js +34 -26
  31. package/dist/core/IndexerEngine.js.map +1 -1
  32. package/dist/core/SearchService.d.ts +1 -0
  33. package/dist/core/SearchService.d.ts.map +1 -1
  34. package/dist/core/SearchService.js +18 -12
  35. package/dist/core/SearchService.js.map +1 -1
  36. package/dist/core/batch-indexer.d.ts.map +1 -1
  37. package/dist/core/batch-indexer.js +5 -13
  38. package/dist/core/batch-indexer.js.map +1 -1
  39. package/dist/core/indexing/IndexFinalizationStage.d.ts.map +1 -1
  40. package/dist/core/indexing/IndexFinalizationStage.js +21 -2
  41. package/dist/core/indexing/IndexFinalizationStage.js.map +1 -1
  42. package/dist/core/indexing/IndexState.d.ts +3 -8
  43. package/dist/core/indexing/IndexState.d.ts.map +1 -1
  44. package/dist/core/indexing/IndexState.js.map +1 -1
  45. package/dist/core/indexing/PersistManager.d.ts +1 -1
  46. package/dist/core/indexing/PersistManager.d.ts.map +1 -1
  47. package/dist/core/indexing/PersistManager.js +17 -17
  48. package/dist/core/indexing/PersistManager.js.map +1 -1
  49. package/dist/core/search/HybridFusion.d.ts +0 -1
  50. package/dist/core/search/HybridFusion.d.ts.map +1 -1
  51. package/dist/core/search/HybridFusion.js +2 -14
  52. package/dist/core/search/HybridFusion.js.map +1 -1
  53. package/dist/core/search/ResultMapper.d.ts +0 -1
  54. package/dist/core/search/ResultMapper.d.ts.map +1 -1
  55. package/dist/core/search/ResultMapper.js +2 -13
  56. package/dist/core/search/ResultMapper.js.map +1 -1
  57. package/dist/core/search/SearchContextManager.d.ts +3 -0
  58. package/dist/core/search/SearchContextManager.d.ts.map +1 -1
  59. package/dist/core/search/SearchContextManager.js +15 -2
  60. package/dist/core/search/SearchContextManager.js.map +1 -1
  61. package/dist/core/search.d.ts.map +1 -1
  62. package/dist/core/search.js +9 -4
  63. package/dist/core/search.js.map +1 -1
  64. package/dist/languages/rules.d.ts.map +1 -1
  65. package/dist/languages/rules.js +14 -5
  66. package/dist/languages/rules.js.map +1 -1
  67. package/dist/mcp/schemas.d.ts +2 -2
  68. package/dist/mcp-server.d.ts +1 -0
  69. package/dist/mcp-server.d.ts.map +1 -1
  70. package/dist/mcp-server.js +32 -21
  71. package/dist/mcp-server.js.map +1 -1
  72. package/dist/providers/base.d.ts +3 -2
  73. package/dist/providers/base.d.ts.map +1 -1
  74. package/dist/providers/base.js +3 -1
  75. package/dist/providers/base.js.map +1 -1
  76. package/dist/providers/chat-llm.d.ts +2 -2
  77. package/dist/providers/chat-llm.d.ts.map +1 -1
  78. package/dist/providers/chat-llm.js +4 -1
  79. package/dist/providers/chat-llm.js.map +1 -1
  80. package/dist/providers/openai.d.ts.map +1 -1
  81. package/dist/providers/openai.js +4 -1
  82. package/dist/providers/openai.js.map +1 -1
  83. package/dist/ranking/api-reranker.d.ts.map +1 -1
  84. package/dist/ranking/api-reranker.js +27 -8
  85. package/dist/ranking/api-reranker.js.map +1 -1
  86. package/dist/ranking/symbol-boost.d.ts.map +1 -1
  87. package/dist/ranking/symbol-boost.js +4 -11
  88. package/dist/ranking/symbol-boost.js.map +1 -1
  89. package/dist/search/bm25.d.ts +10 -0
  90. package/dist/search/bm25.d.ts.map +1 -1
  91. package/dist/search/bm25.js +16 -0
  92. package/dist/search/bm25.js.map +1 -1
  93. package/dist/storage/encrypted-chunks.d.ts +3 -0
  94. package/dist/storage/encrypted-chunks.d.ts.map +1 -1
  95. package/dist/storage/encrypted-chunks.js +108 -16
  96. package/dist/storage/encrypted-chunks.js.map +1 -1
  97. package/dist/synthesis/conversational-synthesizer.d.ts +2 -1
  98. package/dist/synthesis/conversational-synthesizer.d.ts.map +1 -1
  99. package/dist/synthesis/conversational-synthesizer.js +6 -1
  100. package/dist/synthesis/conversational-synthesizer.js.map +1 -1
  101. package/dist/synthesis/prompt-builder.d.ts.map +1 -1
  102. package/dist/synthesis/prompt-builder.js +40 -13
  103. package/dist/synthesis/prompt-builder.js.map +1 -1
  104. package/dist/synthesis/synthesizer.d.ts.map +1 -1
  105. package/dist/synthesis/synthesizer.js +14 -2
  106. package/dist/synthesis/synthesizer.js.map +1 -1
  107. package/dist/tests/api-reranker.test.d.ts +2 -0
  108. package/dist/tests/api-reranker.test.d.ts.map +1 -0
  109. package/dist/tests/api-reranker.test.js +575 -0
  110. package/dist/tests/api-reranker.test.js.map +1 -0
  111. package/dist/tests/bm25.test.d.ts +2 -0
  112. package/dist/tests/bm25.test.d.ts.map +1 -0
  113. package/dist/tests/bm25.test.js +340 -0
  114. package/dist/tests/bm25.test.js.map +1 -0
  115. package/dist/tests/chunking/file-grouper.test.d.ts +2 -0
  116. package/dist/tests/chunking/file-grouper.test.d.ts.map +1 -0
  117. package/dist/tests/chunking/file-grouper.test.js +495 -0
  118. package/dist/tests/chunking/file-grouper.test.js.map +1 -0
  119. package/dist/tests/chunking/semantic-chunker.test.d.ts +2 -0
  120. package/dist/tests/chunking/semantic-chunker.test.d.ts.map +1 -0
  121. package/dist/tests/chunking/semantic-chunker.test.js +509 -0
  122. package/dist/tests/chunking/semantic-chunker.test.js.map +1 -0
  123. package/dist/tests/chunking/token-counter.test.d.ts +2 -0
  124. package/dist/tests/chunking/token-counter.test.d.ts.map +1 -0
  125. package/dist/tests/chunking/token-counter.test.js +441 -0
  126. package/dist/tests/chunking/token-counter.test.js.map +1 -0
  127. package/dist/tests/cli/ask-cmd.test.d.ts +2 -0
  128. package/dist/tests/cli/ask-cmd.test.d.ts.map +1 -0
  129. package/dist/tests/cli/ask-cmd.test.js +152 -0
  130. package/dist/tests/cli/ask-cmd.test.js.map +1 -0
  131. package/dist/tests/cli/chat-cmd.test.d.ts +2 -0
  132. package/dist/tests/cli/chat-cmd.test.d.ts.map +1 -0
  133. package/dist/tests/cli/chat-cmd.test.js +118 -0
  134. package/dist/tests/cli/chat-cmd.test.js.map +1 -0
  135. package/dist/tests/cli/config-cmd.test.d.ts +2 -0
  136. package/dist/tests/cli/config-cmd.test.d.ts.map +1 -0
  137. package/dist/tests/cli/config-cmd.test.js +226 -0
  138. package/dist/tests/cli/config-cmd.test.js.map +1 -0
  139. package/dist/tests/cli/context.test.d.ts +2 -0
  140. package/dist/tests/cli/context.test.d.ts.map +1 -0
  141. package/dist/tests/cli/context.test.js +158 -0
  142. package/dist/tests/cli/context.test.js.map +1 -0
  143. package/dist/tests/cli/index-cmd.test.d.ts +2 -0
  144. package/dist/tests/cli/index-cmd.test.d.ts.map +1 -0
  145. package/dist/tests/cli/index-cmd.test.js +89 -0
  146. package/dist/tests/cli/index-cmd.test.js.map +1 -0
  147. package/dist/tests/cli/index.test.d.ts +2 -0
  148. package/dist/tests/cli/index.test.d.ts.map +1 -0
  149. package/dist/tests/cli/index.test.js +167 -0
  150. package/dist/tests/cli/index.test.js.map +1 -0
  151. package/dist/tests/cli/info-cmd.test.d.ts +2 -0
  152. package/dist/tests/cli/info-cmd.test.d.ts.map +1 -0
  153. package/dist/tests/cli/info-cmd.test.js +47 -0
  154. package/dist/tests/cli/info-cmd.test.js.map +1 -0
  155. package/dist/tests/cli/interactive-config.test.d.ts +2 -0
  156. package/dist/tests/cli/interactive-config.test.d.ts.map +1 -0
  157. package/dist/tests/cli/interactive-config.test.js +30 -0
  158. package/dist/tests/cli/interactive-config.test.js.map +1 -0
  159. package/dist/tests/cli/mcp-cmd.test.d.ts +2 -0
  160. package/dist/tests/cli/mcp-cmd.test.d.ts.map +1 -0
  161. package/dist/tests/cli/mcp-cmd.test.js +47 -0
  162. package/dist/tests/cli/mcp-cmd.test.js.map +1 -0
  163. package/dist/tests/cli/search-cmd.test.d.ts +2 -0
  164. package/dist/tests/cli/search-cmd.test.d.ts.map +1 -0
  165. package/dist/tests/cli/search-cmd.test.js +120 -0
  166. package/dist/tests/cli/search-cmd.test.js.map +1 -0
  167. package/dist/tests/cli/search-with-code-cmd.test.d.ts +2 -0
  168. package/dist/tests/cli/search-with-code-cmd.test.d.ts.map +1 -0
  169. package/dist/tests/cli/search-with-code-cmd.test.js +140 -0
  170. package/dist/tests/cli/search-with-code-cmd.test.js.map +1 -0
  171. package/dist/tests/cli/update-cmd.test.d.ts +2 -0
  172. package/dist/tests/cli/update-cmd.test.d.ts.map +1 -0
  173. package/dist/tests/cli/update-cmd.test.js +75 -0
  174. package/dist/tests/cli/update-cmd.test.js.map +1 -0
  175. package/dist/tests/cli/utils.test.d.ts +2 -0
  176. package/dist/tests/cli/utils.test.d.ts.map +1 -0
  177. package/dist/tests/cli/utils.test.js +119 -0
  178. package/dist/tests/cli/utils.test.js.map +1 -0
  179. package/dist/tests/cli/watch-cmd.test.d.ts +2 -0
  180. package/dist/tests/cli/watch-cmd.test.d.ts.map +1 -0
  181. package/dist/tests/cli/watch-cmd.test.js +84 -0
  182. package/dist/tests/cli/watch-cmd.test.js.map +1 -0
  183. package/dist/tests/cli-ui.test.d.ts +2 -0
  184. package/dist/tests/cli-ui.test.d.ts.map +1 -0
  185. package/dist/tests/cli-ui.test.js +608 -0
  186. package/dist/tests/cli-ui.test.js.map +1 -0
  187. package/dist/tests/codemap-io.test.d.ts +2 -0
  188. package/dist/tests/codemap-io.test.d.ts.map +1 -0
  189. package/dist/tests/codemap-io.test.js +992 -0
  190. package/dist/tests/codemap-io.test.js.map +1 -0
  191. package/dist/tests/config/apply-env.test.d.ts +2 -0
  192. package/dist/tests/config/apply-env.test.d.ts.map +1 -0
  193. package/dist/tests/config/apply-env.test.js +717 -0
  194. package/dist/tests/config/apply-env.test.js.map +1 -0
  195. package/dist/tests/config/constants.test.d.ts +2 -0
  196. package/dist/tests/config/constants.test.d.ts.map +1 -0
  197. package/dist/tests/config/constants.test.js +406 -0
  198. package/dist/tests/config/constants.test.js.map +1 -0
  199. package/dist/tests/config/loader.test.d.ts +2 -0
  200. package/dist/tests/config/loader.test.d.ts.map +1 -0
  201. package/dist/tests/config/loader.test.js +716 -0
  202. package/dist/tests/config/loader.test.js.map +1 -0
  203. package/dist/tests/config/resolver.test.d.ts +2 -0
  204. package/dist/tests/config/resolver.test.d.ts.map +1 -0
  205. package/dist/tests/config/resolver.test.js +402 -0
  206. package/dist/tests/config/resolver.test.js.map +1 -0
  207. package/dist/tests/config/types.test.d.ts +2 -0
  208. package/dist/tests/config/types.test.d.ts.map +1 -0
  209. package/dist/tests/config/types.test.js +460 -0
  210. package/dist/tests/config/types.test.js.map +1 -0
  211. package/dist/tests/context-packs.test.d.ts +2 -0
  212. package/dist/tests/context-packs.test.d.ts.map +1 -0
  213. package/dist/tests/context-packs.test.js +826 -0
  214. package/dist/tests/context-packs.test.js.map +1 -0
  215. package/dist/tests/conversational-synthesizer.test.d.ts +2 -0
  216. package/dist/tests/conversational-synthesizer.test.d.ts.map +1 -0
  217. package/dist/tests/conversational-synthesizer.test.js +595 -0
  218. package/dist/tests/conversational-synthesizer.test.js.map +1 -0
  219. package/dist/tests/database.test.d.ts +2 -0
  220. package/dist/tests/database.test.d.ts.map +1 -0
  221. package/dist/tests/database.test.js +965 -0
  222. package/dist/tests/database.test.js.map +1 -0
  223. package/dist/tests/encrypted-chunks.test.d.ts +2 -0
  224. package/dist/tests/encrypted-chunks.test.d.ts.map +1 -0
  225. package/dist/tests/encrypted-chunks.test.js +1470 -0
  226. package/dist/tests/encrypted-chunks.test.js.map +1 -0
  227. package/dist/tests/hybrid.test.d.ts +2 -0
  228. package/dist/tests/hybrid.test.d.ts.map +1 -0
  229. package/dist/tests/hybrid.test.js +456 -0
  230. package/dist/tests/hybrid.test.js.map +1 -0
  231. package/dist/tests/indexer/ChangeQueue.test.d.ts +12 -0
  232. package/dist/tests/indexer/ChangeQueue.test.d.ts.map +1 -0
  233. package/dist/tests/indexer/ChangeQueue.test.js +441 -0
  234. package/dist/tests/indexer/ChangeQueue.test.js.map +1 -0
  235. package/dist/tests/indexer/ProviderManager.test.d.ts +12 -0
  236. package/dist/tests/indexer/ProviderManager.test.d.ts.map +1 -0
  237. package/dist/tests/indexer/ProviderManager.test.js +290 -0
  238. package/dist/tests/indexer/ProviderManager.test.js.map +1 -0
  239. package/dist/tests/indexer/WatchService.test.d.ts +14 -0
  240. package/dist/tests/indexer/WatchService.test.d.ts.map +1 -0
  241. package/dist/tests/indexer/WatchService.test.js +667 -0
  242. package/dist/tests/indexer/WatchService.test.js.map +1 -0
  243. package/dist/tests/indexer/merkle.test.d.ts +11 -0
  244. package/dist/tests/indexer/merkle.test.d.ts.map +1 -0
  245. package/dist/tests/indexer/merkle.test.js +497 -0
  246. package/dist/tests/indexer/merkle.test.js.map +1 -0
  247. package/dist/tests/indexer/update.test.d.ts +10 -0
  248. package/dist/tests/indexer/update.test.d.ts.map +1 -0
  249. package/dist/tests/indexer/update.test.js +317 -0
  250. package/dist/tests/indexer/update.test.js.map +1 -0
  251. package/dist/tests/indexer/watch.test.d.ts +8 -0
  252. package/dist/tests/indexer/watch.test.d.ts.map +1 -0
  253. package/dist/tests/indexer/watch.test.js +95 -0
  254. package/dist/tests/indexer/watch.test.js.map +1 -0
  255. package/dist/tests/integration/index-search.integration.test.js +1 -0
  256. package/dist/tests/integration/index-search.integration.test.js.map +1 -1
  257. package/dist/tests/languages.test.d.ts +2 -0
  258. package/dist/tests/languages.test.d.ts.map +1 -0
  259. package/dist/tests/languages.test.js +575 -0
  260. package/dist/tests/languages.test.js.map +1 -0
  261. package/dist/tests/logger-redaction.test.d.ts +2 -0
  262. package/dist/tests/logger-redaction.test.d.ts.map +1 -0
  263. package/dist/tests/logger-redaction.test.js +48 -0
  264. package/dist/tests/logger-redaction.test.js.map +1 -0
  265. package/dist/tests/logger.test.d.ts +2 -0
  266. package/dist/tests/logger.test.d.ts.map +1 -0
  267. package/dist/tests/logger.test.js +468 -0
  268. package/dist/tests/logger.test.js.map +1 -0
  269. package/dist/tests/markdown-formatter.test.d.ts +2 -0
  270. package/dist/tests/markdown-formatter.test.d.ts.map +1 -0
  271. package/dist/tests/markdown-formatter.test.js +453 -0
  272. package/dist/tests/markdown-formatter.test.js.map +1 -0
  273. package/dist/tests/mcp/tools/use-context-pack.test.d.ts +7 -0
  274. package/dist/tests/mcp/tools/use-context-pack.test.d.ts.map +1 -0
  275. package/dist/tests/mcp/tools/use-context-pack.test.js +505 -0
  276. package/dist/tests/mcp/tools/use-context-pack.test.js.map +1 -0
  277. package/dist/tests/mutex.test.d.ts +2 -0
  278. package/dist/tests/mutex.test.d.ts.map +1 -0
  279. package/dist/tests/mutex.test.js +489 -0
  280. package/dist/tests/mutex.test.js.map +1 -0
  281. package/dist/tests/path-helpers.test.d.ts +2 -0
  282. package/dist/tests/path-helpers.test.d.ts.map +1 -0
  283. package/dist/tests/path-helpers.test.js +332 -0
  284. package/dist/tests/path-helpers.test.js.map +1 -0
  285. package/dist/tests/prompt-builder.test.d.ts +2 -0
  286. package/dist/tests/prompt-builder.test.d.ts.map +1 -0
  287. package/dist/tests/prompt-builder.test.js +417 -0
  288. package/dist/tests/prompt-builder.test.js.map +1 -0
  289. package/dist/tests/providers/base.test.d.ts +2 -0
  290. package/dist/tests/providers/base.test.d.ts.map +1 -0
  291. package/dist/tests/providers/base.test.js +299 -0
  292. package/dist/tests/providers/base.test.js.map +1 -0
  293. package/dist/tests/providers/chat-llm.test.d.ts +2 -0
  294. package/dist/tests/providers/chat-llm.test.d.ts.map +1 -0
  295. package/dist/tests/providers/chat-llm.test.js +435 -0
  296. package/dist/tests/providers/chat-llm.test.js.map +1 -0
  297. package/dist/tests/providers/index.test.d.ts +2 -0
  298. package/dist/tests/providers/index.test.d.ts.map +1 -0
  299. package/dist/tests/providers/index.test.js +204 -0
  300. package/dist/tests/providers/index.test.js.map +1 -0
  301. package/dist/tests/providers/mock.test.d.ts +2 -0
  302. package/dist/tests/providers/mock.test.d.ts.map +1 -0
  303. package/dist/tests/providers/mock.test.js +225 -0
  304. package/dist/tests/providers/mock.test.js.map +1 -0
  305. package/dist/tests/providers/openai.test.d.ts +2 -0
  306. package/dist/tests/providers/openai.test.d.ts.map +1 -0
  307. package/dist/tests/providers/openai.test.js +408 -0
  308. package/dist/tests/providers/openai.test.js.map +1 -0
  309. package/dist/tests/providers/token-counter.test.d.ts +2 -0
  310. package/dist/tests/providers/token-counter.test.d.ts.map +1 -0
  311. package/dist/tests/providers/token-counter.test.js +247 -0
  312. package/dist/tests/providers/token-counter.test.js.map +1 -0
  313. package/dist/tests/rate-limiter.test.js +392 -1
  314. package/dist/tests/rate-limiter.test.js.map +1 -1
  315. package/dist/tests/scope.test.d.ts +2 -0
  316. package/dist/tests/scope.test.d.ts.map +1 -0
  317. package/dist/tests/scope.test.js +529 -0
  318. package/dist/tests/scope.test.js.map +1 -0
  319. package/dist/tests/simple-lru.test.js +377 -0
  320. package/dist/tests/simple-lru.test.js.map +1 -1
  321. package/dist/tests/symbol-boost.test.js +730 -10
  322. package/dist/tests/symbol-boost.test.js.map +1 -1
  323. package/dist/tests/symbols-extract.test.d.ts +2 -0
  324. package/dist/tests/symbols-extract.test.d.ts.map +1 -0
  325. package/dist/tests/symbols-extract.test.js +536 -0
  326. package/dist/tests/symbols-extract.test.js.map +1 -0
  327. package/dist/tests/symbols-graph.test.d.ts +2 -0
  328. package/dist/tests/symbols-graph.test.d.ts.map +1 -0
  329. package/dist/tests/symbols-graph.test.js +656 -0
  330. package/dist/tests/symbols-graph.test.js.map +1 -0
  331. package/dist/tests/synthesizer.test.d.ts +2 -0
  332. package/dist/tests/synthesizer.test.d.ts.map +1 -0
  333. package/dist/tests/synthesizer.test.js +381 -0
  334. package/dist/tests/synthesizer.test.js.map +1 -0
  335. package/dist/types/context-pack.d.ts +3 -3
  336. package/dist/utils/logger.d.ts +5 -1
  337. package/dist/utils/logger.d.ts.map +1 -1
  338. package/dist/utils/logger.js +149 -4
  339. package/dist/utils/logger.js.map +1 -1
  340. package/dist/utils/mutex.d.ts +7 -2
  341. package/dist/utils/mutex.d.ts.map +1 -1
  342. package/dist/utils/mutex.js +31 -7
  343. package/dist/utils/mutex.js.map +1 -1
  344. package/dist/utils/path-helpers.d.ts.map +1 -1
  345. package/dist/utils/path-helpers.js +5 -2
  346. package/dist/utils/path-helpers.js.map +1 -1
  347. package/dist/utils/simple-lru.d.ts +6 -0
  348. package/dist/utils/simple-lru.d.ts.map +1 -1
  349. package/dist/utils/simple-lru.js +26 -0
  350. package/dist/utils/simple-lru.js.map +1 -1
  351. package/package.json +1 -1
@@ -0,0 +1,965 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import { CodeVaultDatabase, decodeEmbedding, initDatabase } from '../database/db.js';
6
+ /**
7
+ * Comprehensive unit tests for the CodeVault database module.
8
+ * Tests cover CRUD operations, binary embedding handling, transaction management,
9
+ * SQL injection prevention, and connection lifecycle.
10
+ */
11
+ // Helper to create a temporary database for tests
12
+ async function createTempDb() {
13
+ const tmpDir = await fs.mkdtemp(path.join(process.cwd(), 'tmp-db-test-'));
14
+ const dbPath = path.join(tmpDir, 'test.db');
15
+ const db = new CodeVaultDatabase(dbPath);
16
+ db.initialize(1536);
17
+ return {
18
+ db,
19
+ dbPath,
20
+ cleanup: async () => {
21
+ db.close();
22
+ await fs.rm(tmpDir, { recursive: true, force: true });
23
+ }
24
+ };
25
+ }
26
+ // Helper to create a valid chunk for testing
27
+ function createTestChunk(overrides = {}) {
28
+ return {
29
+ id: 'test-chunk-1',
30
+ file_path: '/src/example.ts',
31
+ symbol: 'testFunction',
32
+ sha: 'abc123def456',
33
+ lang: 'typescript',
34
+ chunk_type: 'function',
35
+ embedding: new Float32Array([0.1, 0.2, 0.3, 0.4, 0.5]),
36
+ embedding_provider: 'openai',
37
+ embedding_dimensions: 5,
38
+ codevault_tags: ['utility', 'helper'],
39
+ codevault_intent: 'Test function for unit tests',
40
+ codevault_description: 'A test function that does nothing',
41
+ doc_comments: '/** Test documentation */',
42
+ variables_used: ['foo', 'bar'],
43
+ context_info: { tested: true, coverage: 100 },
44
+ ...overrides
45
+ };
46
+ }
47
+ // ============================================================================
48
+ // Embedding Encoding/Decoding Tests
49
+ // ============================================================================
50
+ test('decodeEmbedding handles empty buffer', () => {
51
+ const result = decodeEmbedding(Buffer.alloc(0));
52
+ assert.equal(result.length, 0);
53
+ });
54
+ test('decodeEmbedding handles null/undefined buffer', () => {
55
+ // @ts-expect-error Testing null handling
56
+ const resultNull = decodeEmbedding(null);
57
+ assert.equal(resultNull.length, 0);
58
+ // @ts-expect-error Testing undefined handling
59
+ const resultUndefined = decodeEmbedding(undefined);
60
+ assert.equal(resultUndefined.length, 0);
61
+ });
62
+ test('decodeEmbedding handles buffer with incorrect size (not divisible by 4)', () => {
63
+ const result = decodeEmbedding(Buffer.from([1, 2, 3]));
64
+ assert.equal(result.length, 0);
65
+ });
66
+ test('decodeEmbedding handles buffer too small (less than 4 bytes)', () => {
67
+ const result = decodeEmbedding(Buffer.from([1, 2]));
68
+ assert.equal(result.length, 0);
69
+ });
70
+ test('decodeEmbedding correctly decodes binary Float32 buffer', () => {
71
+ // Create a buffer with known float values
72
+ const originalValues = [1.5, -2.5, 0.0, 3.14159];
73
+ const buffer = Buffer.allocUnsafe(originalValues.length * 4);
74
+ for (let i = 0; i < originalValues.length; i++) {
75
+ buffer.writeFloatLE(originalValues[i], i * 4);
76
+ }
77
+ const result = decodeEmbedding(buffer);
78
+ assert.equal(result.length, 4);
79
+ assert.ok(Math.abs(result[0] - 1.5) < 0.0001);
80
+ assert.ok(Math.abs(result[1] - (-2.5)) < 0.0001);
81
+ assert.ok(Math.abs(result[2] - 0.0) < 0.0001);
82
+ assert.ok(Math.abs(result[3] - 3.14159) < 0.0001);
83
+ });
84
+ test('decodeEmbedding respects dimensions parameter', () => {
85
+ const buffer = Buffer.allocUnsafe(16); // 4 floats
86
+ for (let i = 0; i < 4; i++) {
87
+ buffer.writeFloatLE(i * 1.0, i * 4);
88
+ }
89
+ // Request only 2 dimensions
90
+ const result = decodeEmbedding(buffer, 2);
91
+ assert.equal(result.length, 2);
92
+ assert.ok(Math.abs(result[0] - 0.0) < 0.0001);
93
+ assert.ok(Math.abs(result[1] - 1.0) < 0.0001);
94
+ });
95
+ test('decodeEmbedding handles dimensions larger than buffer', () => {
96
+ const buffer = Buffer.allocUnsafe(8); // 2 floats
97
+ buffer.writeFloatLE(1.0, 0);
98
+ buffer.writeFloatLE(2.0, 4);
99
+ // Request 10 dimensions but only 2 available
100
+ const result = decodeEmbedding(buffer, 10);
101
+ assert.equal(result.length, 2);
102
+ });
103
+ test('decodeEmbedding parses JSON array format', () => {
104
+ const jsonArray = [0.1, 0.2, 0.3];
105
+ const buffer = Buffer.from(JSON.stringify(jsonArray), 'utf8');
106
+ const result = decodeEmbedding(buffer);
107
+ assert.equal(result.length, 3);
108
+ assert.ok(Math.abs(result[0] - 0.1) < 0.0001);
109
+ assert.ok(Math.abs(result[1] - 0.2) < 0.0001);
110
+ assert.ok(Math.abs(result[2] - 0.3) < 0.0001);
111
+ });
112
+ test('decodeEmbedding handles JSON with negative numbers', () => {
113
+ const jsonArray = [-0.5, -1.0];
114
+ const buffer = Buffer.from(JSON.stringify(jsonArray), 'utf8');
115
+ const result = decodeEmbedding(buffer);
116
+ assert.equal(result.length, 2);
117
+ assert.ok(Math.abs(result[0] - (-0.5)) < 0.0001);
118
+ });
119
+ test('decodeEmbedding rejects non-array JSON', () => {
120
+ const jsonObject = { embedding: [1, 2, 3] };
121
+ const buffer = Buffer.from(JSON.stringify(jsonObject), 'utf8');
122
+ // Should fall back to binary parsing which will fail due to size
123
+ const result = decodeEmbedding(buffer);
124
+ // Since the buffer starts with '{', it will try JSON parse, fail to get array, return null
125
+ // Then binary decode will check size and likely return empty
126
+ assert.equal(result.length, 0);
127
+ });
128
+ test('decodeEmbedding handles malformed JSON gracefully', () => {
129
+ const buffer = Buffer.from('[1, 2, 3', 'utf8'); // Invalid JSON
130
+ const result = decodeEmbedding(buffer);
131
+ // Falls back to binary parsing
132
+ assert.ok(result.length >= 0); // Should not throw
133
+ });
134
+ // ============================================================================
135
+ // Database Initialization and Schema Tests
136
+ // ============================================================================
137
+ test('CodeVaultDatabase creates tables on construction', async () => {
138
+ const { db, cleanup } = await createTempDb();
139
+ try {
140
+ // Verify code_chunks table exists by attempting to query it
141
+ const chunks = db.getChunks('openai', 1536);
142
+ assert.ok(Array.isArray(chunks));
143
+ assert.equal(chunks.length, 0);
144
+ }
145
+ finally {
146
+ await cleanup();
147
+ }
148
+ });
149
+ test('CodeVaultDatabase initialization creates additional tables and indexes', async () => {
150
+ const { db, cleanup } = await createTempDb();
151
+ try {
152
+ // After initialization, intention_cache and query_patterns should exist
153
+ // We can verify by attempting operations that use them
154
+ db.recordQueryPattern('test-pattern');
155
+ db.recordIntention('normalized', 'original', 'sha123', 0.95);
156
+ // Should not throw
157
+ assert.ok(true);
158
+ }
159
+ finally {
160
+ await cleanup();
161
+ }
162
+ });
163
+ test('initDatabase creates directory structure if missing', async () => {
164
+ const tmpDir = await fs.mkdtemp(path.join(process.cwd(), 'tmp-init-'));
165
+ const testPath = path.join(tmpDir, 'nested', 'project');
166
+ try {
167
+ await fs.mkdir(testPath, { recursive: true });
168
+ initDatabase(1536, testPath);
169
+ // Verify the .codevault directory was created
170
+ const codevaultDir = path.join(testPath, '.codevault');
171
+ const stat = await fs.stat(codevaultDir);
172
+ assert.ok(stat.isDirectory());
173
+ // Verify database file exists
174
+ const dbPath = path.join(codevaultDir, 'codevault.db');
175
+ const dbStat = await fs.stat(dbPath);
176
+ assert.ok(dbStat.isFile());
177
+ }
178
+ finally {
179
+ await fs.rm(tmpDir, { recursive: true, force: true });
180
+ }
181
+ });
182
+ // ============================================================================
183
+ // Chunk CRUD Operations Tests
184
+ // ============================================================================
185
+ test('insertChunk inserts a single chunk successfully', async () => {
186
+ const { db, cleanup } = await createTempDb();
187
+ try {
188
+ const chunk = createTestChunk();
189
+ db.insertChunk(chunk);
190
+ const chunks = db.getChunks('openai', 5);
191
+ assert.equal(chunks.length, 1);
192
+ assert.equal(chunks[0].id, 'test-chunk-1');
193
+ assert.equal(chunks[0].file_path, '/src/example.ts');
194
+ assert.equal(chunks[0].symbol, 'testFunction');
195
+ assert.equal(chunks[0].lang, 'typescript');
196
+ }
197
+ finally {
198
+ await cleanup();
199
+ }
200
+ });
201
+ test('insertChunk updates existing chunk on conflict (REPLACE)', async () => {
202
+ const { db, cleanup } = await createTempDb();
203
+ try {
204
+ const chunk1 = createTestChunk({ symbol: 'originalSymbol' });
205
+ db.insertChunk(chunk1);
206
+ const chunk2 = createTestChunk({ symbol: 'updatedSymbol' });
207
+ db.insertChunk(chunk2);
208
+ const chunks = db.getChunks('openai', 5);
209
+ assert.equal(chunks.length, 1);
210
+ assert.equal(chunks[0].symbol, 'updatedSymbol');
211
+ }
212
+ finally {
213
+ await cleanup();
214
+ }
215
+ });
216
+ test('insertChunk encodes embeddings as binary Float32', async () => {
217
+ const { db, cleanup } = await createTempDb();
218
+ try {
219
+ const originalEmbedding = new Float32Array([1.5, -2.5, 0.0, 3.14]);
220
+ const chunk = createTestChunk({
221
+ embedding: originalEmbedding,
222
+ embedding_dimensions: 4
223
+ });
224
+ db.insertChunk(chunk);
225
+ const chunks = db.getChunks('openai', 4);
226
+ assert.equal(chunks.length, 1);
227
+ // Decode the embedding and verify values
228
+ const decoded = decodeEmbedding(chunks[0].embedding, 4);
229
+ assert.equal(decoded.length, 4);
230
+ assert.ok(Math.abs(decoded[0] - 1.5) < 0.0001);
231
+ assert.ok(Math.abs(decoded[1] - (-2.5)) < 0.0001);
232
+ assert.ok(Math.abs(decoded[2] - 0.0) < 0.0001);
233
+ assert.ok(Math.abs(decoded[3] - 3.14) < 0.01);
234
+ }
235
+ finally {
236
+ await cleanup();
237
+ }
238
+ });
239
+ test('insertChunk handles NaN and special values in embeddings', async () => {
240
+ const { db, cleanup } = await createTempDb();
241
+ try {
242
+ // The encoding function uses `Number(x) || 0` which:
243
+ // - NaN becomes 0 (falsy)
244
+ // - Infinity passes through (truthy)
245
+ // - -Infinity passes through (truthy)
246
+ const embedding = [NaN, Infinity, -Infinity, 1.0];
247
+ const chunk = createTestChunk({
248
+ embedding,
249
+ embedding_dimensions: 4
250
+ });
251
+ db.insertChunk(chunk);
252
+ const chunks = db.getChunks('openai', 4);
253
+ const decoded = decodeEmbedding(chunks[0].embedding, 4);
254
+ // NaN becomes 0 (falsy value triggers || 0)
255
+ assert.equal(decoded[0], 0);
256
+ // Infinity and -Infinity pass through (truthy values)
257
+ assert.equal(decoded[1], Infinity);
258
+ assert.equal(decoded[2], -Infinity);
259
+ assert.ok(Math.abs(decoded[3] - 1.0) < 0.0001);
260
+ }
261
+ finally {
262
+ await cleanup();
263
+ }
264
+ });
265
+ test('insertChunks inserts multiple chunks in a batch', async () => {
266
+ const { db, cleanup } = await createTempDb();
267
+ try {
268
+ const chunks = [
269
+ createTestChunk({ id: 'chunk-1', symbol: 'func1' }),
270
+ createTestChunk({ id: 'chunk-2', symbol: 'func2' }),
271
+ createTestChunk({ id: 'chunk-3', symbol: 'func3' })
272
+ ];
273
+ db.insertChunks(chunks);
274
+ const result = db.getChunks('openai', 5);
275
+ assert.equal(result.length, 3);
276
+ }
277
+ finally {
278
+ await cleanup();
279
+ }
280
+ });
281
+ test('insertChunks handles empty array gracefully', async () => {
282
+ const { db, cleanup } = await createTempDb();
283
+ try {
284
+ db.insertChunks([]);
285
+ const result = db.getChunks('openai', 5);
286
+ assert.equal(result.length, 0);
287
+ }
288
+ finally {
289
+ await cleanup();
290
+ }
291
+ });
292
+ test('insertChunks handles non-array input gracefully', async () => {
293
+ const { db, cleanup } = await createTempDb();
294
+ try {
295
+ // @ts-expect-error Testing non-array input
296
+ db.insertChunks(null);
297
+ // @ts-expect-error Testing undefined input
298
+ db.insertChunks(undefined);
299
+ const result = db.getChunks('openai', 5);
300
+ assert.equal(result.length, 0);
301
+ }
302
+ finally {
303
+ await cleanup();
304
+ }
305
+ });
306
+ test('deleteChunks removes specified chunks by ID', async () => {
307
+ const { db, cleanup } = await createTempDb();
308
+ try {
309
+ const chunks = [
310
+ createTestChunk({ id: 'chunk-1', symbol: 'func1' }),
311
+ createTestChunk({ id: 'chunk-2', symbol: 'func2' }),
312
+ createTestChunk({ id: 'chunk-3', symbol: 'func3' })
313
+ ];
314
+ db.insertChunks(chunks);
315
+ db.deleteChunks(['chunk-1', 'chunk-3']);
316
+ const result = db.getChunks('openai', 5);
317
+ assert.equal(result.length, 1);
318
+ assert.equal(result[0].id, 'chunk-2');
319
+ }
320
+ finally {
321
+ await cleanup();
322
+ }
323
+ });
324
+ test('deleteChunks handles empty array gracefully', async () => {
325
+ const { db, cleanup } = await createTempDb();
326
+ try {
327
+ db.insertChunk(createTestChunk());
328
+ db.deleteChunks([]);
329
+ const result = db.getChunks('openai', 5);
330
+ assert.equal(result.length, 1);
331
+ }
332
+ finally {
333
+ await cleanup();
334
+ }
335
+ });
336
+ test('deleteChunks handles non-existent IDs gracefully', async () => {
337
+ const { db, cleanup } = await createTempDb();
338
+ try {
339
+ db.insertChunk(createTestChunk({ id: 'existing-chunk' }));
340
+ db.deleteChunks(['non-existent-1', 'non-existent-2']);
341
+ const result = db.getChunks('openai', 5);
342
+ assert.equal(result.length, 1);
343
+ }
344
+ finally {
345
+ await cleanup();
346
+ }
347
+ });
348
+ test('getChunks filters by provider and dimensions', async () => {
349
+ const { db, cleanup } = await createTempDb();
350
+ try {
351
+ db.insertChunk(createTestChunk({
352
+ id: 'openai-chunk',
353
+ embedding_provider: 'openai',
354
+ embedding_dimensions: 1536
355
+ }));
356
+ db.insertChunk(createTestChunk({
357
+ id: 'anthropic-chunk',
358
+ embedding_provider: 'anthropic',
359
+ embedding_dimensions: 1024
360
+ }));
361
+ const openaiChunks = db.getChunks('openai', 1536);
362
+ assert.equal(openaiChunks.length, 1);
363
+ assert.equal(openaiChunks[0].id, 'openai-chunk');
364
+ const anthropicChunks = db.getChunks('anthropic', 1024);
365
+ assert.equal(anthropicChunks.length, 1);
366
+ assert.equal(anthropicChunks[0].id, 'anthropic-chunk');
367
+ // No match
368
+ const noMatch = db.getChunks('openai', 1024);
369
+ assert.equal(noMatch.length, 0);
370
+ }
371
+ finally {
372
+ await cleanup();
373
+ }
374
+ });
375
+ test('getOverviewChunks returns limited chunk overview', async () => {
376
+ const { db, cleanup } = await createTempDb();
377
+ try {
378
+ for (let i = 0; i < 10; i++) {
379
+ db.insertChunk(createTestChunk({
380
+ id: `chunk-${i}`,
381
+ symbol: `func${i}`,
382
+ file_path: `/src/file${i}.ts`
383
+ }));
384
+ }
385
+ const overview = db.getOverviewChunks(5);
386
+ assert.equal(overview.length, 5);
387
+ assert.ok(overview[0].id);
388
+ assert.ok(overview[0].file_path);
389
+ assert.ok(overview[0].symbol);
390
+ assert.ok(overview[0].sha);
391
+ assert.ok(overview[0].lang);
392
+ }
393
+ finally {
394
+ await cleanup();
395
+ }
396
+ });
397
+ test('getExistingDimensions returns distinct provider/dimension pairs', async () => {
398
+ const { db, cleanup } = await createTempDb();
399
+ try {
400
+ db.insertChunk(createTestChunk({
401
+ id: 'chunk-1',
402
+ embedding_provider: 'openai',
403
+ embedding_dimensions: 1536
404
+ }));
405
+ db.insertChunk(createTestChunk({
406
+ id: 'chunk-2',
407
+ embedding_provider: 'openai',
408
+ embedding_dimensions: 1536
409
+ }));
410
+ db.insertChunk(createTestChunk({
411
+ id: 'chunk-3',
412
+ embedding_provider: 'anthropic',
413
+ embedding_dimensions: 1024
414
+ }));
415
+ const dims = db.getExistingDimensions();
416
+ assert.equal(dims.length, 2);
417
+ const openaiDim = dims.find(d => d.embedding_provider === 'openai');
418
+ const anthropicDim = dims.find(d => d.embedding_provider === 'anthropic');
419
+ assert.ok(openaiDim);
420
+ assert.equal(openaiDim.embedding_dimensions, 1536);
421
+ assert.ok(anthropicDim);
422
+ assert.equal(anthropicDim.embedding_dimensions, 1024);
423
+ }
424
+ finally {
425
+ await cleanup();
426
+ }
427
+ });
428
+ // ============================================================================
429
+ // File Path Operations Tests
430
+ // ============================================================================
431
+ test('getAllFilePaths returns distinct file paths', async () => {
432
+ const { db, cleanup } = await createTempDb();
433
+ try {
434
+ db.insertChunk(createTestChunk({ id: 'c1', file_path: '/src/a.ts' }));
435
+ db.insertChunk(createTestChunk({ id: 'c2', file_path: '/src/a.ts' }));
436
+ db.insertChunk(createTestChunk({ id: 'c3', file_path: '/src/b.ts' }));
437
+ const paths = db.getAllFilePaths();
438
+ assert.equal(paths.length, 2);
439
+ assert.ok(paths.includes('/src/a.ts'));
440
+ assert.ok(paths.includes('/src/b.ts'));
441
+ }
442
+ finally {
443
+ await cleanup();
444
+ }
445
+ });
446
+ test('deleteChunksByFilePath removes all chunks for a file', async () => {
447
+ const { db, cleanup } = await createTempDb();
448
+ try {
449
+ db.insertChunk(createTestChunk({ id: 'c1', file_path: '/src/a.ts' }));
450
+ db.insertChunk(createTestChunk({ id: 'c2', file_path: '/src/a.ts' }));
451
+ db.insertChunk(createTestChunk({ id: 'c3', file_path: '/src/b.ts' }));
452
+ db.deleteChunksByFilePath('/src/a.ts');
453
+ const paths = db.getAllFilePaths();
454
+ assert.equal(paths.length, 1);
455
+ assert.ok(paths.includes('/src/b.ts'));
456
+ }
457
+ finally {
458
+ await cleanup();
459
+ }
460
+ });
461
+ // ============================================================================
462
+ // Intention Cache Tests
463
+ // ============================================================================
464
+ test('recordIntention inserts new intention record', async () => {
465
+ const { db, cleanup } = await createTempDb();
466
+ try {
467
+ db.recordIntention('find auth', 'find authentication code', 'sha-123', 0.95);
468
+ const result = db.searchByIntention('find auth');
469
+ assert.ok(result);
470
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
471
+ const typedResult = result;
472
+ assert.equal(typedResult.target_sha, 'sha-123');
473
+ assert.ok(Math.abs(typedResult.confidence - 0.95) < 0.001);
474
+ assert.equal(typedResult.original_query, 'find authentication code');
475
+ }
476
+ finally {
477
+ await cleanup();
478
+ }
479
+ });
480
+ test('recordIntention updates existing intention with same query/sha', async () => {
481
+ const { db, cleanup } = await createTempDb();
482
+ try {
483
+ db.recordIntention('find auth', 'original', 'sha-123', 0.8);
484
+ db.recordIntention('find auth', 'updated', 'sha-123', 0.95);
485
+ const result = db.searchByIntention('find auth');
486
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
487
+ const typedResult = result;
488
+ // Confidence should be updated
489
+ assert.ok(Math.abs(typedResult.confidence - 0.95) < 0.001);
490
+ // Usage count should be incremented
491
+ assert.equal(typedResult.usage_count, 2);
492
+ }
493
+ finally {
494
+ await cleanup();
495
+ }
496
+ });
497
+ test('searchByIntention returns null for non-existent query', async () => {
498
+ const { db, cleanup } = await createTempDb();
499
+ try {
500
+ const result = db.searchByIntention('non-existent query');
501
+ assert.equal(result, undefined);
502
+ }
503
+ finally {
504
+ await cleanup();
505
+ }
506
+ });
507
+ test('searchByIntention joins with code_chunks table', async () => {
508
+ const { db, cleanup } = await createTempDb();
509
+ try {
510
+ db.insertChunk(createTestChunk({
511
+ id: 'chunk-1',
512
+ sha: 'matched-sha',
513
+ file_path: '/src/auth.ts',
514
+ symbol: 'authenticate'
515
+ }));
516
+ db.recordIntention('auth code', 'find auth code', 'matched-sha', 0.9);
517
+ const result = db.searchByIntention('auth code');
518
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
519
+ const typedResult = result;
520
+ assert.equal(typedResult.file_path, '/src/auth.ts');
521
+ assert.equal(typedResult.symbol, 'authenticate');
522
+ }
523
+ finally {
524
+ await cleanup();
525
+ }
526
+ });
527
+ // ============================================================================
528
+ // Query Pattern Tests
529
+ // ============================================================================
530
+ test('recordQueryPattern inserts new pattern', async () => {
531
+ const { db, cleanup } = await createTempDb();
532
+ try {
533
+ db.recordQueryPattern('authentication');
534
+ db.recordQueryPattern('database query');
535
+ // No direct getter, but should not throw
536
+ assert.ok(true);
537
+ }
538
+ finally {
539
+ await cleanup();
540
+ }
541
+ });
542
+ test('recordQueryPattern increments frequency for existing pattern', async () => {
543
+ const { db, cleanup } = await createTempDb();
544
+ try {
545
+ db.recordQueryPattern('auth');
546
+ db.recordQueryPattern('auth');
547
+ db.recordQueryPattern('auth');
548
+ // No direct getter, but should not throw
549
+ assert.ok(true);
550
+ }
551
+ finally {
552
+ await cleanup();
553
+ }
554
+ });
555
+ // ============================================================================
556
+ // Transaction Management Tests
557
+ // ============================================================================
558
+ test('transaction commits on success', async () => {
559
+ const { db, cleanup } = await createTempDb();
560
+ try {
561
+ db.transaction(() => {
562
+ db.insertChunk(createTestChunk({ id: 'tx-chunk-1' }));
563
+ db.insertChunk(createTestChunk({ id: 'tx-chunk-2' }));
564
+ });
565
+ const chunks = db.getChunks('openai', 5);
566
+ assert.equal(chunks.length, 2);
567
+ }
568
+ finally {
569
+ await cleanup();
570
+ }
571
+ });
572
+ test('transaction rolls back on error', async () => {
573
+ const { db, cleanup } = await createTempDb();
574
+ try {
575
+ try {
576
+ db.transaction(() => {
577
+ db.insertChunk(createTestChunk({ id: 'rollback-chunk' }));
578
+ throw new Error('Intentional error for rollback');
579
+ });
580
+ }
581
+ catch {
582
+ // Expected
583
+ }
584
+ const chunks = db.getChunks('openai', 5);
585
+ assert.equal(chunks.length, 0);
586
+ }
587
+ finally {
588
+ await cleanup();
589
+ }
590
+ });
591
+ test('transaction rejects async functions', async () => {
592
+ const { db, cleanup } = await createTempDb();
593
+ try {
594
+ assert.throws(() => {
595
+ db.transaction(() => {
596
+ // Return a promise-like object
597
+ return Promise.resolve('async result');
598
+ });
599
+ }, /better-sqlite3 transactions must be synchronous/);
600
+ }
601
+ finally {
602
+ await cleanup();
603
+ }
604
+ });
605
+ test('manual transaction control (beginTransaction/commit)', async () => {
606
+ const { db, cleanup } = await createTempDb();
607
+ try {
608
+ db.beginTransaction();
609
+ db.insertChunk(createTestChunk({ id: 'manual-tx-chunk' }));
610
+ db.commit();
611
+ const chunks = db.getChunks('openai', 5);
612
+ assert.equal(chunks.length, 1);
613
+ }
614
+ finally {
615
+ await cleanup();
616
+ }
617
+ });
618
+ test('manual transaction rollback', async () => {
619
+ const { db, cleanup } = await createTempDb();
620
+ try {
621
+ db.beginTransaction();
622
+ db.insertChunk(createTestChunk({ id: 'rollback-chunk' }));
623
+ db.rollback();
624
+ const chunks = db.getChunks('openai', 5);
625
+ assert.equal(chunks.length, 0);
626
+ }
627
+ finally {
628
+ await cleanup();
629
+ }
630
+ });
631
+ // ============================================================================
632
+ // Database Statistics and Connection Tests
633
+ // ============================================================================
634
+ test('getStats returns correct database state', async () => {
635
+ const { db, cleanup } = await createTempDb();
636
+ try {
637
+ const stats = db.getStats();
638
+ assert.equal(stats.isOpen, true);
639
+ assert.equal(stats.inTransaction, false);
640
+ assert.equal(stats.readonly, false);
641
+ assert.equal(stats.memory, false); // We're using a file-based DB
642
+ }
643
+ finally {
644
+ await cleanup();
645
+ }
646
+ });
647
+ test('getStats reflects transaction state', async () => {
648
+ const { db, cleanup } = await createTempDb();
649
+ try {
650
+ db.beginTransaction();
651
+ const stats = db.getStats();
652
+ assert.equal(stats.inTransaction, true);
653
+ db.rollback();
654
+ }
655
+ finally {
656
+ await cleanup();
657
+ }
658
+ });
659
+ test('close properly closes database connection', async () => {
660
+ const { db, dbPath, cleanup } = await createTempDb();
661
+ try {
662
+ const statsBefore = db.getStats();
663
+ assert.equal(statsBefore.isOpen, true);
664
+ db.close();
665
+ const statsAfter = db.getStats();
666
+ assert.equal(statsAfter.isOpen, false);
667
+ }
668
+ finally {
669
+ // cleanup will try to close again, but that's okay
670
+ await fs.rm(path.dirname(dbPath), { recursive: true, force: true });
671
+ }
672
+ });
673
+ // ============================================================================
674
+ // SQL Injection Prevention Tests (Parameterized Queries)
675
+ // ============================================================================
676
+ test('insertChunk is safe from SQL injection in id field', async () => {
677
+ const { db, cleanup } = await createTempDb();
678
+ try {
679
+ const maliciousId = "'; DROP TABLE code_chunks; --";
680
+ const chunk = createTestChunk({ id: maliciousId });
681
+ db.insertChunk(chunk);
682
+ // Table should still exist and chunk should be inserted with malicious ID as literal
683
+ const chunks = db.getChunks('openai', 5);
684
+ assert.equal(chunks.length, 1);
685
+ assert.equal(chunks[0].id, maliciousId);
686
+ }
687
+ finally {
688
+ await cleanup();
689
+ }
690
+ });
691
+ test('insertChunk is safe from SQL injection in file_path field', async () => {
692
+ const { db, cleanup } = await createTempDb();
693
+ try {
694
+ const maliciousPath = "/src/'; DELETE FROM code_chunks WHERE '1'='1";
695
+ const chunk = createTestChunk({ file_path: maliciousPath });
696
+ db.insertChunk(chunk);
697
+ const chunks = db.getChunks('openai', 5);
698
+ assert.equal(chunks.length, 1);
699
+ assert.equal(chunks[0].file_path, maliciousPath);
700
+ }
701
+ finally {
702
+ await cleanup();
703
+ }
704
+ });
705
+ test('deleteChunks is safe from SQL injection', async () => {
706
+ const { db, cleanup } = await createTempDb();
707
+ try {
708
+ db.insertChunk(createTestChunk({ id: 'safe-chunk' }));
709
+ const maliciousIds = ["'; DROP TABLE code_chunks; --", "1' OR '1'='1"];
710
+ db.deleteChunks(maliciousIds);
711
+ // Table should still exist
712
+ const chunks = db.getChunks('openai', 5);
713
+ assert.equal(chunks.length, 1);
714
+ assert.equal(chunks[0].id, 'safe-chunk');
715
+ }
716
+ finally {
717
+ await cleanup();
718
+ }
719
+ });
720
+ test('getChunks is safe from SQL injection in provider name', async () => {
721
+ const { db, cleanup } = await createTempDb();
722
+ try {
723
+ db.insertChunk(createTestChunk({ embedding_provider: 'safe-provider' }));
724
+ const maliciousProvider = "'; DROP TABLE code_chunks; --";
725
+ const result = db.getChunks(maliciousProvider, 5);
726
+ // Should return empty (no match), table still exists
727
+ assert.equal(result.length, 0);
728
+ const safeResult = db.getChunks('safe-provider', 5);
729
+ assert.equal(safeResult.length, 1);
730
+ }
731
+ finally {
732
+ await cleanup();
733
+ }
734
+ });
735
+ test('recordIntention is safe from SQL injection', async () => {
736
+ const { db, cleanup } = await createTempDb();
737
+ try {
738
+ const maliciousQuery = "'; DELETE FROM intention_cache; --";
739
+ db.recordIntention(maliciousQuery, 'original', 'sha-123', 0.9);
740
+ // Should not throw and intention should be recorded
741
+ const result = db.searchByIntention(maliciousQuery);
742
+ assert.ok(result);
743
+ }
744
+ finally {
745
+ await cleanup();
746
+ }
747
+ });
748
+ test('searchByIntention is safe from SQL injection', async () => {
749
+ const { db, cleanup } = await createTempDb();
750
+ try {
751
+ db.recordIntention('safe-query', 'original', 'sha-123', 0.9);
752
+ const maliciousQuery = "' OR '1'='1";
753
+ const result = db.searchByIntention(maliciousQuery);
754
+ // Should return undefined (no match), not all records
755
+ assert.equal(result, undefined);
756
+ }
757
+ finally {
758
+ await cleanup();
759
+ }
760
+ });
761
+ test('recordQueryPattern is safe from SQL injection', async () => {
762
+ const { db, cleanup } = await createTempDb();
763
+ try {
764
+ const maliciousPattern = "'; DROP TABLE query_patterns; --";
765
+ db.recordQueryPattern(maliciousPattern);
766
+ // Should not throw, table should still exist
767
+ db.recordQueryPattern('safe-pattern');
768
+ assert.ok(true);
769
+ }
770
+ finally {
771
+ await cleanup();
772
+ }
773
+ });
774
+ test('getOverviewChunks is safe from SQL injection in limit', async () => {
775
+ const { db, cleanup } = await createTempDb();
776
+ try {
777
+ db.insertChunk(createTestChunk());
778
+ // Limit is a number, so SQL injection is not directly possible here
779
+ // But we verify the function works correctly with valid numeric input
780
+ const result = db.getOverviewChunks(10);
781
+ assert.ok(Array.isArray(result));
782
+ }
783
+ finally {
784
+ await cleanup();
785
+ }
786
+ });
787
+ test('deleteChunksByFilePath is safe from SQL injection', async () => {
788
+ const { db, cleanup } = await createTempDb();
789
+ try {
790
+ db.insertChunk(createTestChunk({ id: 'chunk-1', file_path: '/safe/path.ts' }));
791
+ const maliciousPath = "'; DELETE FROM code_chunks WHERE '1'='1";
792
+ db.deleteChunksByFilePath(maliciousPath);
793
+ // Original chunk should still exist
794
+ const chunks = db.getChunks('openai', 5);
795
+ assert.equal(chunks.length, 1);
796
+ }
797
+ finally {
798
+ await cleanup();
799
+ }
800
+ });
801
+ // ============================================================================
802
+ // Edge Cases and Error Handling Tests
803
+ // ============================================================================
804
+ test('handles very large embedding arrays', async () => {
805
+ const { db, cleanup } = await createTempDb();
806
+ try {
807
+ const largeEmbedding = new Float32Array(3072); // Large embedding dimension
808
+ for (let i = 0; i < largeEmbedding.length; i++) {
809
+ largeEmbedding[i] = Math.random();
810
+ }
811
+ const chunk = createTestChunk({
812
+ embedding: largeEmbedding,
813
+ embedding_dimensions: 3072
814
+ });
815
+ db.insertChunk(chunk);
816
+ const chunks = db.getChunks('openai', 3072);
817
+ assert.equal(chunks.length, 1);
818
+ const decoded = decodeEmbedding(chunks[0].embedding, 3072);
819
+ assert.equal(decoded.length, 3072);
820
+ // Verify round-trip accuracy
821
+ for (let i = 0; i < 10; i++) {
822
+ assert.ok(Math.abs(decoded[i] - largeEmbedding[i]) < 0.0001);
823
+ }
824
+ }
825
+ finally {
826
+ await cleanup();
827
+ }
828
+ });
829
+ test('handles unicode characters in chunk fields', async () => {
830
+ const { db, cleanup } = await createTempDb();
831
+ try {
832
+ const chunk = createTestChunk({
833
+ id: 'unicode-chunk-123',
834
+ symbol: 'testFunction',
835
+ file_path: '/src/example.ts',
836
+ codevault_description: 'Description with unicode and special chars',
837
+ doc_comments: '/** Japanese docs and more unicode chars */'
838
+ });
839
+ db.insertChunk(chunk);
840
+ const chunks = db.getChunks('openai', 5);
841
+ assert.equal(chunks.length, 1);
842
+ assert.ok(chunks[0].codevault_description?.includes('unicode'));
843
+ }
844
+ finally {
845
+ await cleanup();
846
+ }
847
+ });
848
+ test('handles empty strings in chunk fields', async () => {
849
+ const { db, cleanup } = await createTempDb();
850
+ try {
851
+ const chunk = createTestChunk({
852
+ symbol: '',
853
+ codevault_intent: '',
854
+ codevault_description: '',
855
+ doc_comments: ''
856
+ });
857
+ db.insertChunk(chunk);
858
+ const chunks = db.getChunks('openai', 5);
859
+ assert.equal(chunks.length, 1);
860
+ assert.equal(chunks[0].symbol, '');
861
+ }
862
+ finally {
863
+ await cleanup();
864
+ }
865
+ });
866
+ test('handles null values in optional chunk fields', async () => {
867
+ const { db, cleanup } = await createTempDb();
868
+ try {
869
+ const chunk = createTestChunk({
870
+ codevault_intent: null,
871
+ codevault_description: null,
872
+ doc_comments: null
873
+ });
874
+ db.insertChunk(chunk);
875
+ const chunks = db.getChunks('openai', 5);
876
+ assert.equal(chunks.length, 1);
877
+ assert.equal(chunks[0].codevault_intent, null);
878
+ }
879
+ finally {
880
+ await cleanup();
881
+ }
882
+ });
883
+ test('handles concurrent insertions without data corruption', async () => {
884
+ const { db, cleanup } = await createTempDb();
885
+ try {
886
+ // Insert many chunks rapidly
887
+ const promises = [];
888
+ for (let i = 0; i < 100; i++) {
889
+ promises.push(new Promise((resolve) => {
890
+ db.insertChunk(createTestChunk({ id: `concurrent-${i}` }));
891
+ resolve();
892
+ }));
893
+ }
894
+ await Promise.all(promises);
895
+ const chunks = db.getChunks('openai', 5);
896
+ assert.equal(chunks.length, 100);
897
+ }
898
+ finally {
899
+ await cleanup();
900
+ }
901
+ });
902
+ test('batch insert handles large batches efficiently', async () => {
903
+ const { db, cleanup } = await createTempDb();
904
+ try {
905
+ const chunks = [];
906
+ for (let i = 0; i < 500; i++) {
907
+ chunks.push(createTestChunk({ id: `batch-${i}`, symbol: `func${i}` }));
908
+ }
909
+ const startTime = Date.now();
910
+ db.insertChunks(chunks);
911
+ const endTime = Date.now();
912
+ // Should complete in reasonable time (< 5 seconds for 500 items)
913
+ assert.ok(endTime - startTime < 5000);
914
+ const result = db.getChunks('openai', 5);
915
+ assert.equal(result.length, 500);
916
+ }
917
+ finally {
918
+ await cleanup();
919
+ }
920
+ });
921
+ // ============================================================================
922
+ // In-Memory Database Tests
923
+ // ============================================================================
924
+ test('works correctly with in-memory database', async () => {
925
+ const db = new CodeVaultDatabase(':memory:');
926
+ db.initialize(1536);
927
+ try {
928
+ const stats = db.getStats();
929
+ assert.equal(stats.memory, true);
930
+ assert.equal(stats.isOpen, true);
931
+ db.insertChunk(createTestChunk({ id: 'memory-chunk' }));
932
+ const chunks = db.getChunks('openai', 5);
933
+ assert.equal(chunks.length, 1);
934
+ assert.equal(chunks[0].id, 'memory-chunk');
935
+ }
936
+ finally {
937
+ db.close();
938
+ }
939
+ });
940
+ test('in-memory database supports all operations', async () => {
941
+ const db = new CodeVaultDatabase(':memory:');
942
+ db.initialize(1536);
943
+ try {
944
+ // Test all major operations
945
+ db.insertChunk(createTestChunk({ id: 'chunk-1', file_path: '/a.ts' }));
946
+ db.insertChunks([
947
+ createTestChunk({ id: 'chunk-2', file_path: '/b.ts' }),
948
+ createTestChunk({ id: 'chunk-3', file_path: '/b.ts' })
949
+ ]);
950
+ const paths = db.getAllFilePaths();
951
+ assert.equal(paths.length, 2);
952
+ db.deleteChunksByFilePath('/b.ts');
953
+ assert.equal(db.getAllFilePaths().length, 1);
954
+ db.recordIntention('test', 'test query', 'sha-123', 0.9);
955
+ db.recordQueryPattern('test pattern');
956
+ const overview = db.getOverviewChunks(10);
957
+ assert.equal(overview.length, 1);
958
+ const dims = db.getExistingDimensions();
959
+ assert.ok(dims.length >= 1);
960
+ }
961
+ finally {
962
+ db.close();
963
+ }
964
+ });
965
+ //# sourceMappingURL=database.test.js.map