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,716 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import os from 'os';
6
+ import { readGlobalConfig, readProjectConfig, readEnvConfig, loadConfig, getConfigSources, saveGlobalConfig, saveProjectConfig, hasGlobalConfig, hasProjectConfig, getGlobalConfigPath, getProjectConfigPath, } from '../../config/loader.js';
7
+ /**
8
+ * Helper to create a temporary directory for tests
9
+ */
10
+ function createTempDir() {
11
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'codevault-test-'));
12
+ }
13
+ /**
14
+ * Helper to clean up temporary directory
15
+ */
16
+ function cleanupTempDir(dir) {
17
+ if (fs.existsSync(dir)) {
18
+ fs.rmSync(dir, { recursive: true, force: true });
19
+ }
20
+ }
21
+ /**
22
+ * Helper to save original env and restore later
23
+ */
24
+ function saveEnv() {
25
+ const envVars = [
26
+ 'CODEVAULT_EMBEDDING_API_KEY',
27
+ 'OPENAI_API_KEY',
28
+ 'CODEVAULT_EMBEDDING_BASE_URL',
29
+ 'OPENAI_BASE_URL',
30
+ 'CODEVAULT_EMBEDDING_MODEL',
31
+ 'CODEVAULT_OPENAI_EMBEDDING_MODEL',
32
+ 'OPENAI_MODEL',
33
+ 'CODEVAULT_EMBEDDING_DIMENSIONS',
34
+ 'CODEVAULT_DIMENSIONS',
35
+ 'CODEVAULT_EMBEDDING_MAX_TOKENS',
36
+ 'CODEVAULT_MAX_TOKENS',
37
+ 'CODEVAULT_EMBEDDING_RATE_LIMIT_RPM',
38
+ 'CODEVAULT_RATE_LIMIT_RPM',
39
+ 'CODEVAULT_RATE_LIMIT',
40
+ 'CODEVAULT_EMBEDDING_RATE_LIMIT_TPM',
41
+ 'CODEVAULT_RATE_LIMIT_TPM',
42
+ 'CODEVAULT_ENCRYPTION_KEY',
43
+ 'CODEVAULT_RERANK_API_URL',
44
+ 'CODEVAULT_RERANK_API_KEY',
45
+ 'CODEVAULT_RERANK_MODEL',
46
+ 'CODEVAULT_CHAT_API_KEY',
47
+ 'CODEVAULT_CHAT_BASE_URL',
48
+ 'CODEVAULT_CHAT_MODEL',
49
+ 'CODEVAULT_OPENAI_CHAT_MODEL',
50
+ 'CODEVAULT_CHAT_MAX_TOKENS',
51
+ 'CODEVAULT_CHAT_TEMPERATURE',
52
+ ];
53
+ const saved = {};
54
+ for (const key of envVars) {
55
+ saved[key] = process.env[key];
56
+ }
57
+ return saved;
58
+ }
59
+ function restoreEnv(saved) {
60
+ for (const [key, value] of Object.entries(saved)) {
61
+ if (value === undefined) {
62
+ delete process.env[key];
63
+ }
64
+ else {
65
+ process.env[key] = value;
66
+ }
67
+ }
68
+ }
69
+ function clearEnv(keys) {
70
+ for (const key of keys) {
71
+ delete process.env[key];
72
+ }
73
+ }
74
+ // =============================================================================
75
+ // readGlobalConfig tests
76
+ // =============================================================================
77
+ test('readGlobalConfig returns null when file does not exist', () => {
78
+ // This test relies on the actual global config path
79
+ // We cannot easily mock fs.existsSync in Node.js test runner
80
+ // So we verify the function returns a config or null without error
81
+ const result = readGlobalConfig();
82
+ assert.ok(result === null || typeof result === 'object', 'should return null or config object');
83
+ });
84
+ // =============================================================================
85
+ // readProjectConfig tests
86
+ // =============================================================================
87
+ test('readProjectConfig returns null when config file does not exist', () => {
88
+ const tempDir = createTempDir();
89
+ try {
90
+ const result = readProjectConfig(tempDir);
91
+ assert.equal(result, null, 'should return null for non-existent config');
92
+ }
93
+ finally {
94
+ cleanupTempDir(tempDir);
95
+ }
96
+ });
97
+ test('readProjectConfig reads valid JSON config', () => {
98
+ const tempDir = createTempDir();
99
+ const configDir = path.join(tempDir, '.codevault');
100
+ const configPath = path.join(configDir, 'config.json');
101
+ try {
102
+ fs.mkdirSync(configDir, { recursive: true });
103
+ const testConfig = {
104
+ defaultProvider: 'openai',
105
+ maxTokens: 4096,
106
+ providers: {
107
+ openai: {
108
+ apiKey: 'test-key-123',
109
+ model: 'text-embedding-3-small'
110
+ }
111
+ }
112
+ };
113
+ fs.writeFileSync(configPath, JSON.stringify(testConfig, null, 2));
114
+ const result = readProjectConfig(tempDir);
115
+ assert.ok(result !== null, 'should return config object');
116
+ assert.equal(result?.defaultProvider, 'openai');
117
+ assert.equal(result?.maxTokens, 4096);
118
+ assert.equal(result?.providers?.openai?.apiKey, 'test-key-123');
119
+ assert.equal(result?.providers?.openai?.model, 'text-embedding-3-small');
120
+ }
121
+ finally {
122
+ cleanupTempDir(tempDir);
123
+ }
124
+ });
125
+ test('readProjectConfig returns null for invalid JSON', () => {
126
+ const tempDir = createTempDir();
127
+ const configDir = path.join(tempDir, '.codevault');
128
+ const configPath = path.join(configDir, 'config.json');
129
+ try {
130
+ fs.mkdirSync(configDir, { recursive: true });
131
+ fs.writeFileSync(configPath, 'invalid json {{{');
132
+ const result = readProjectConfig(tempDir);
133
+ assert.equal(result, null, 'should return null for invalid JSON');
134
+ }
135
+ finally {
136
+ cleanupTempDir(tempDir);
137
+ }
138
+ });
139
+ test('readProjectConfig resolves relative basePath', () => {
140
+ const tempDir = createTempDir();
141
+ const configDir = path.join(tempDir, '.codevault');
142
+ const configPath = path.join(configDir, 'config.json');
143
+ try {
144
+ fs.mkdirSync(configDir, { recursive: true });
145
+ fs.writeFileSync(configPath, JSON.stringify({ maxTokens: 1000 }));
146
+ // Change to temp dir to test relative path resolution
147
+ const originalCwd = process.cwd();
148
+ process.chdir(tempDir);
149
+ try {
150
+ const result = readProjectConfig('.');
151
+ assert.ok(result !== null, 'should read config with relative path');
152
+ assert.equal(result?.maxTokens, 1000);
153
+ }
154
+ finally {
155
+ process.chdir(originalCwd);
156
+ }
157
+ }
158
+ finally {
159
+ cleanupTempDir(tempDir);
160
+ }
161
+ });
162
+ // =============================================================================
163
+ // readEnvConfig tests
164
+ // =============================================================================
165
+ test('readEnvConfig returns empty object when no env vars set', () => {
166
+ const saved = saveEnv();
167
+ clearEnv(Object.keys(saved));
168
+ try {
169
+ const result = readEnvConfig();
170
+ assert.ok(typeof result === 'object', 'should return an object');
171
+ // Should not have providers if no env vars are set
172
+ assert.equal(result.providers, undefined, 'should not have providers');
173
+ }
174
+ finally {
175
+ restoreEnv(saved);
176
+ }
177
+ });
178
+ test('readEnvConfig reads CODEVAULT_EMBEDDING_API_KEY', () => {
179
+ const saved = saveEnv();
180
+ clearEnv(Object.keys(saved));
181
+ try {
182
+ process.env.CODEVAULT_EMBEDDING_API_KEY = 'test-api-key';
183
+ const result = readEnvConfig();
184
+ assert.equal(result.providers?.openai?.apiKey, 'test-api-key');
185
+ }
186
+ finally {
187
+ restoreEnv(saved);
188
+ }
189
+ });
190
+ test('readEnvConfig falls back to OPENAI_API_KEY', () => {
191
+ const saved = saveEnv();
192
+ clearEnv(Object.keys(saved));
193
+ try {
194
+ process.env.OPENAI_API_KEY = 'openai-fallback-key';
195
+ const result = readEnvConfig();
196
+ assert.equal(result.providers?.openai?.apiKey, 'openai-fallback-key');
197
+ }
198
+ finally {
199
+ restoreEnv(saved);
200
+ }
201
+ });
202
+ test('readEnvConfig prefers CODEVAULT_EMBEDDING_API_KEY over OPENAI_API_KEY', () => {
203
+ const saved = saveEnv();
204
+ clearEnv(Object.keys(saved));
205
+ try {
206
+ process.env.CODEVAULT_EMBEDDING_API_KEY = 'codevault-key';
207
+ process.env.OPENAI_API_KEY = 'openai-key';
208
+ const result = readEnvConfig();
209
+ assert.equal(result.providers?.openai?.apiKey, 'codevault-key');
210
+ }
211
+ finally {
212
+ restoreEnv(saved);
213
+ }
214
+ });
215
+ test('readEnvConfig reads base URL with fallback', () => {
216
+ const saved = saveEnv();
217
+ clearEnv(Object.keys(saved));
218
+ try {
219
+ process.env.CODEVAULT_EMBEDDING_BASE_URL = 'https://api.example.com';
220
+ const result = readEnvConfig();
221
+ assert.equal(result.providers?.openai?.baseUrl, 'https://api.example.com');
222
+ }
223
+ finally {
224
+ restoreEnv(saved);
225
+ }
226
+ });
227
+ test('readEnvConfig reads embedding model with multiple fallbacks', () => {
228
+ const saved = saveEnv();
229
+ clearEnv(Object.keys(saved));
230
+ try {
231
+ // Test first priority
232
+ process.env.CODEVAULT_EMBEDDING_MODEL = 'model-v1';
233
+ let result = readEnvConfig();
234
+ assert.equal(result.providers?.openai?.model, 'model-v1');
235
+ // Test second priority
236
+ delete process.env.CODEVAULT_EMBEDDING_MODEL;
237
+ process.env.CODEVAULT_OPENAI_EMBEDDING_MODEL = 'model-v2';
238
+ result = readEnvConfig();
239
+ assert.equal(result.providers?.openai?.model, 'model-v2');
240
+ // Test third priority (OPENAI_MODEL)
241
+ delete process.env.CODEVAULT_OPENAI_EMBEDDING_MODEL;
242
+ process.env.OPENAI_MODEL = 'model-v3';
243
+ result = readEnvConfig();
244
+ assert.equal(result.providers?.openai?.model, 'model-v3');
245
+ }
246
+ finally {
247
+ restoreEnv(saved);
248
+ }
249
+ });
250
+ test('readEnvConfig parses dimensions correctly', () => {
251
+ const saved = saveEnv();
252
+ clearEnv(Object.keys(saved));
253
+ try {
254
+ // Need to set API key first for the dimensions to be applied
255
+ process.env.CODEVAULT_EMBEDDING_API_KEY = 'test-key';
256
+ process.env.CODEVAULT_EMBEDDING_DIMENSIONS = '1536';
257
+ const result = readEnvConfig();
258
+ assert.equal(result.providers?.openai?.dimensions, 1536);
259
+ }
260
+ finally {
261
+ restoreEnv(saved);
262
+ }
263
+ });
264
+ test('readEnvConfig ignores invalid dimensions', () => {
265
+ const saved = saveEnv();
266
+ clearEnv(Object.keys(saved));
267
+ try {
268
+ process.env.CODEVAULT_EMBEDDING_API_KEY = 'test-key';
269
+ process.env.CODEVAULT_EMBEDDING_DIMENSIONS = 'not-a-number';
270
+ const result = readEnvConfig();
271
+ assert.equal(result.providers?.openai?.dimensions, undefined);
272
+ }
273
+ finally {
274
+ restoreEnv(saved);
275
+ }
276
+ });
277
+ test('readEnvConfig ignores zero or negative dimensions', () => {
278
+ const saved = saveEnv();
279
+ clearEnv(Object.keys(saved));
280
+ try {
281
+ process.env.CODEVAULT_EMBEDDING_API_KEY = 'test-key';
282
+ process.env.CODEVAULT_EMBEDDING_DIMENSIONS = '0';
283
+ let result = readEnvConfig();
284
+ assert.equal(result.providers?.openai?.dimensions, undefined);
285
+ process.env.CODEVAULT_EMBEDDING_DIMENSIONS = '-100';
286
+ result = readEnvConfig();
287
+ assert.equal(result.providers?.openai?.dimensions, undefined);
288
+ }
289
+ finally {
290
+ restoreEnv(saved);
291
+ }
292
+ });
293
+ test('readEnvConfig parses maxTokens correctly', () => {
294
+ const saved = saveEnv();
295
+ clearEnv(Object.keys(saved));
296
+ try {
297
+ process.env.CODEVAULT_EMBEDDING_MAX_TOKENS = '8192';
298
+ const result = readEnvConfig();
299
+ assert.equal(result.maxTokens, 8192);
300
+ }
301
+ finally {
302
+ restoreEnv(saved);
303
+ }
304
+ });
305
+ test('readEnvConfig parses rate limits correctly', () => {
306
+ const saved = saveEnv();
307
+ clearEnv(Object.keys(saved));
308
+ try {
309
+ process.env.CODEVAULT_EMBEDDING_RATE_LIMIT_RPM = '100';
310
+ process.env.CODEVAULT_EMBEDDING_RATE_LIMIT_TPM = '50000';
311
+ const result = readEnvConfig();
312
+ assert.equal(result.rateLimit?.rpm, 100);
313
+ assert.equal(result.rateLimit?.tpm, 50000);
314
+ }
315
+ finally {
316
+ restoreEnv(saved);
317
+ }
318
+ });
319
+ test('readEnvConfig reads encryption key and enables encryption', () => {
320
+ const saved = saveEnv();
321
+ clearEnv(Object.keys(saved));
322
+ try {
323
+ process.env.CODEVAULT_ENCRYPTION_KEY = 'my-secret-encryption-key';
324
+ const result = readEnvConfig();
325
+ assert.equal(result.encryption?.key, 'my-secret-encryption-key');
326
+ assert.equal(result.encryption?.enabled, true);
327
+ }
328
+ finally {
329
+ restoreEnv(saved);
330
+ }
331
+ });
332
+ test('readEnvConfig reads reranker settings', () => {
333
+ const saved = saveEnv();
334
+ clearEnv(Object.keys(saved));
335
+ try {
336
+ process.env.CODEVAULT_RERANK_API_URL = 'https://reranker.example.com';
337
+ process.env.CODEVAULT_RERANK_API_KEY = 'reranker-key';
338
+ process.env.CODEVAULT_RERANK_MODEL = 'rerank-v1';
339
+ const result = readEnvConfig();
340
+ assert.equal(result.reranker?.apiUrl, 'https://reranker.example.com');
341
+ assert.equal(result.reranker?.apiKey, 'reranker-key');
342
+ assert.equal(result.reranker?.model, 'rerank-v1');
343
+ }
344
+ finally {
345
+ restoreEnv(saved);
346
+ }
347
+ });
348
+ test('readEnvConfig reads chat LLM settings', () => {
349
+ const saved = saveEnv();
350
+ clearEnv(Object.keys(saved));
351
+ try {
352
+ process.env.CODEVAULT_CHAT_API_KEY = 'chat-api-key';
353
+ process.env.CODEVAULT_CHAT_BASE_URL = 'https://chat.example.com';
354
+ process.env.CODEVAULT_CHAT_MODEL = 'gpt-4';
355
+ process.env.CODEVAULT_CHAT_MAX_TOKENS = '4000';
356
+ process.env.CODEVAULT_CHAT_TEMPERATURE = '0.7';
357
+ const result = readEnvConfig();
358
+ assert.equal(result.chatLLM?.openai?.apiKey, 'chat-api-key');
359
+ assert.equal(result.chatLLM?.openai?.baseUrl, 'https://chat.example.com');
360
+ assert.equal(result.chatLLM?.openai?.model, 'gpt-4');
361
+ assert.equal(result.chatLLM?.openai?.maxTokens, 4000);
362
+ assert.equal(result.chatLLM?.openai?.temperature, 0.7);
363
+ }
364
+ finally {
365
+ restoreEnv(saved);
366
+ }
367
+ });
368
+ test('readEnvConfig caps chat max tokens at 256000', () => {
369
+ const saved = saveEnv();
370
+ clearEnv(Object.keys(saved));
371
+ try {
372
+ process.env.CODEVAULT_CHAT_MAX_TOKENS = '500000';
373
+ const result = readEnvConfig();
374
+ assert.equal(result.chatLLM?.openai?.maxTokens, 256000);
375
+ }
376
+ finally {
377
+ restoreEnv(saved);
378
+ }
379
+ });
380
+ test('readEnvConfig chat falls back to OPENAI_API_KEY', () => {
381
+ const saved = saveEnv();
382
+ clearEnv(Object.keys(saved));
383
+ try {
384
+ process.env.OPENAI_API_KEY = 'shared-openai-key';
385
+ const result = readEnvConfig();
386
+ assert.equal(result.providers?.openai?.apiKey, 'shared-openai-key');
387
+ assert.equal(result.chatLLM?.openai?.apiKey, 'shared-openai-key');
388
+ }
389
+ finally {
390
+ restoreEnv(saved);
391
+ }
392
+ });
393
+ // =============================================================================
394
+ // loadConfig tests (integration of all sources)
395
+ // =============================================================================
396
+ test('loadConfig merges configs with correct priority (env > project > global)', () => {
397
+ const tempDir = createTempDir();
398
+ const configDir = path.join(tempDir, '.codevault');
399
+ const saved = saveEnv();
400
+ clearEnv(Object.keys(saved));
401
+ try {
402
+ // Create project config
403
+ fs.mkdirSync(configDir, { recursive: true });
404
+ const projectConfig = {
405
+ maxTokens: 2000,
406
+ providers: {
407
+ openai: {
408
+ model: 'project-model'
409
+ }
410
+ }
411
+ };
412
+ fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(projectConfig));
413
+ // Set env var that should override project config
414
+ process.env.CODEVAULT_EMBEDDING_MAX_TOKENS = '4000';
415
+ const result = loadConfig(tempDir);
416
+ // Env should override project
417
+ assert.equal(result.maxTokens, 4000, 'env should override project maxTokens');
418
+ // Project config should still be present for non-overridden values
419
+ assert.equal(result.providers?.openai?.model, 'project-model', 'project model should be preserved');
420
+ }
421
+ finally {
422
+ restoreEnv(saved);
423
+ cleanupTempDir(tempDir);
424
+ }
425
+ });
426
+ test('loadConfig returns empty object when no config sources exist', () => {
427
+ const tempDir = createTempDir();
428
+ const saved = saveEnv();
429
+ clearEnv(Object.keys(saved));
430
+ try {
431
+ const result = loadConfig(tempDir);
432
+ assert.ok(typeof result === 'object', 'should return an object');
433
+ }
434
+ finally {
435
+ restoreEnv(saved);
436
+ cleanupTempDir(tempDir);
437
+ }
438
+ });
439
+ test('loadConfig deep merges nested provider objects', () => {
440
+ const tempDir = createTempDir();
441
+ const configDir = path.join(tempDir, '.codevault');
442
+ const saved = saveEnv();
443
+ clearEnv(Object.keys(saved));
444
+ try {
445
+ fs.mkdirSync(configDir, { recursive: true });
446
+ const projectConfig = {
447
+ providers: {
448
+ openai: {
449
+ model: 'project-model',
450
+ dimensions: 1536
451
+ }
452
+ }
453
+ };
454
+ fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(projectConfig));
455
+ // Set only API key in env
456
+ process.env.CODEVAULT_EMBEDDING_API_KEY = 'env-api-key';
457
+ const result = loadConfig(tempDir);
458
+ // Should have both env and project values
459
+ assert.equal(result.providers?.openai?.apiKey, 'env-api-key', 'should have env API key');
460
+ assert.equal(result.providers?.openai?.model, 'project-model', 'should preserve project model');
461
+ assert.equal(result.providers?.openai?.dimensions, 1536, 'should preserve project dimensions');
462
+ }
463
+ finally {
464
+ restoreEnv(saved);
465
+ cleanupTempDir(tempDir);
466
+ }
467
+ });
468
+ test('loadConfig deep merges rate limit config', () => {
469
+ const tempDir = createTempDir();
470
+ const configDir = path.join(tempDir, '.codevault');
471
+ const saved = saveEnv();
472
+ clearEnv(Object.keys(saved));
473
+ try {
474
+ fs.mkdirSync(configDir, { recursive: true });
475
+ const projectConfig = {
476
+ rateLimit: {
477
+ rpm: 50,
478
+ tpm: 10000
479
+ }
480
+ };
481
+ fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(projectConfig));
482
+ // Override only RPM via env
483
+ process.env.CODEVAULT_EMBEDDING_RATE_LIMIT_RPM = '100';
484
+ const result = loadConfig(tempDir);
485
+ assert.equal(result.rateLimit?.rpm, 100, 'should override RPM from env');
486
+ assert.equal(result.rateLimit?.tpm, 10000, 'should preserve TPM from project');
487
+ }
488
+ finally {
489
+ restoreEnv(saved);
490
+ cleanupTempDir(tempDir);
491
+ }
492
+ });
493
+ test('loadConfig deep merges chatLLM config', () => {
494
+ const tempDir = createTempDir();
495
+ const configDir = path.join(tempDir, '.codevault');
496
+ const saved = saveEnv();
497
+ clearEnv(Object.keys(saved));
498
+ try {
499
+ fs.mkdirSync(configDir, { recursive: true });
500
+ const projectConfig = {
501
+ chatLLM: {
502
+ openai: {
503
+ model: 'gpt-4',
504
+ temperature: 0.5
505
+ }
506
+ }
507
+ };
508
+ fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(projectConfig));
509
+ // Override only API key via env
510
+ process.env.CODEVAULT_CHAT_API_KEY = 'chat-env-key';
511
+ const result = loadConfig(tempDir);
512
+ assert.equal(result.chatLLM?.openai?.apiKey, 'chat-env-key', 'should have env API key');
513
+ assert.equal(result.chatLLM?.openai?.model, 'gpt-4', 'should preserve project model');
514
+ assert.equal(result.chatLLM?.openai?.temperature, 0.5, 'should preserve project temperature');
515
+ }
516
+ finally {
517
+ restoreEnv(saved);
518
+ cleanupTempDir(tempDir);
519
+ }
520
+ });
521
+ // =============================================================================
522
+ // getConfigSources tests
523
+ // =============================================================================
524
+ test('getConfigSources returns all three sources', () => {
525
+ const tempDir = createTempDir();
526
+ const saved = saveEnv();
527
+ clearEnv(Object.keys(saved));
528
+ try {
529
+ const sources = getConfigSources(tempDir);
530
+ assert.ok('global' in sources, 'should have global key');
531
+ assert.ok('project' in sources, 'should have project key');
532
+ assert.ok('env' in sources, 'should have env key');
533
+ }
534
+ finally {
535
+ restoreEnv(saved);
536
+ cleanupTempDir(tempDir);
537
+ }
538
+ });
539
+ test('getConfigSources returns null for missing global and project', () => {
540
+ const tempDir = createTempDir();
541
+ const saved = saveEnv();
542
+ clearEnv(Object.keys(saved));
543
+ try {
544
+ const sources = getConfigSources(tempDir);
545
+ // Project should be null (no config in temp dir)
546
+ assert.equal(sources.project, null, 'project should be null');
547
+ // Env should always be an object
548
+ assert.ok(typeof sources.env === 'object', 'env should be object');
549
+ }
550
+ finally {
551
+ restoreEnv(saved);
552
+ cleanupTempDir(tempDir);
553
+ }
554
+ });
555
+ // =============================================================================
556
+ // saveGlobalConfig tests
557
+ // =============================================================================
558
+ test('saveGlobalConfig creates directory and file', () => {
559
+ // We cannot easily test the actual global config path without mocking
560
+ // This test verifies the function signature and return type
561
+ // In a real environment, we would use dependency injection or mock fs
562
+ // Just verify the function exists and has correct signature
563
+ assert.ok(typeof saveGlobalConfig === 'function');
564
+ });
565
+ // =============================================================================
566
+ // saveProjectConfig tests
567
+ // =============================================================================
568
+ test('saveProjectConfig creates directory and writes config', () => {
569
+ const tempDir = createTempDir();
570
+ const configDir = path.join(tempDir, '.codevault');
571
+ const configPath = path.join(configDir, 'config.json');
572
+ try {
573
+ const testConfig = {
574
+ defaultProvider: 'openai',
575
+ maxTokens: 8192,
576
+ providers: {
577
+ openai: {
578
+ apiKey: 'saved-key',
579
+ model: 'text-embedding-3-large'
580
+ }
581
+ }
582
+ };
583
+ saveProjectConfig(testConfig, tempDir);
584
+ assert.ok(fs.existsSync(configPath), 'config file should exist');
585
+ const savedContent = JSON.parse(fs.readFileSync(configPath, 'utf8'));
586
+ assert.equal(savedContent.defaultProvider, 'openai');
587
+ assert.equal(savedContent.maxTokens, 8192);
588
+ assert.equal(savedContent.providers?.openai?.apiKey, 'saved-key');
589
+ }
590
+ finally {
591
+ cleanupTempDir(tempDir);
592
+ }
593
+ });
594
+ test('saveProjectConfig overwrites existing config', () => {
595
+ const tempDir = createTempDir();
596
+ const configDir = path.join(tempDir, '.codevault');
597
+ const configPath = path.join(configDir, 'config.json');
598
+ try {
599
+ fs.mkdirSync(configDir, { recursive: true });
600
+ fs.writeFileSync(configPath, JSON.stringify({ maxTokens: 1000 }));
601
+ saveProjectConfig({ maxTokens: 2000 }, tempDir);
602
+ const savedContent = JSON.parse(fs.readFileSync(configPath, 'utf8'));
603
+ assert.equal(savedContent.maxTokens, 2000, 'should overwrite with new value');
604
+ }
605
+ finally {
606
+ cleanupTempDir(tempDir);
607
+ }
608
+ });
609
+ // =============================================================================
610
+ // hasGlobalConfig tests
611
+ // =============================================================================
612
+ test('hasGlobalConfig returns boolean', () => {
613
+ const result = hasGlobalConfig();
614
+ assert.ok(typeof result === 'boolean', 'should return a boolean');
615
+ });
616
+ // =============================================================================
617
+ // hasProjectConfig tests
618
+ // =============================================================================
619
+ test('hasProjectConfig returns false for non-existent config', () => {
620
+ const tempDir = createTempDir();
621
+ try {
622
+ const result = hasProjectConfig(tempDir);
623
+ assert.equal(result, false, 'should return false when config does not exist');
624
+ }
625
+ finally {
626
+ cleanupTempDir(tempDir);
627
+ }
628
+ });
629
+ test('hasProjectConfig returns true for existing config', () => {
630
+ const tempDir = createTempDir();
631
+ const configDir = path.join(tempDir, '.codevault');
632
+ try {
633
+ fs.mkdirSync(configDir, { recursive: true });
634
+ fs.writeFileSync(path.join(configDir, 'config.json'), '{}');
635
+ const result = hasProjectConfig(tempDir);
636
+ assert.equal(result, true, 'should return true when config exists');
637
+ }
638
+ finally {
639
+ cleanupTempDir(tempDir);
640
+ }
641
+ });
642
+ // =============================================================================
643
+ // getGlobalConfigPath tests
644
+ // =============================================================================
645
+ test('getGlobalConfigPath returns expected path', () => {
646
+ const result = getGlobalConfigPath();
647
+ assert.ok(result.includes('.codevault'), 'should contain .codevault');
648
+ assert.ok(result.includes('config.json'), 'should contain config.json');
649
+ assert.ok(result.startsWith(os.homedir()), 'should start with home directory');
650
+ });
651
+ // =============================================================================
652
+ // getProjectConfigPath tests
653
+ // =============================================================================
654
+ test('getProjectConfigPath returns expected path with basePath', () => {
655
+ const result = getProjectConfigPath('/some/project');
656
+ assert.ok(result.includes('.codevault'), 'should contain .codevault');
657
+ assert.ok(result.includes('config.json'), 'should contain config.json');
658
+ assert.ok(result.startsWith('/some/project'), 'should start with basePath');
659
+ });
660
+ test('getProjectConfigPath resolves relative basePath', () => {
661
+ const result = getProjectConfigPath('.');
662
+ assert.ok(result.includes('.codevault'), 'should contain .codevault');
663
+ assert.ok(result.includes('config.json'), 'should contain config.json');
664
+ assert.ok(path.isAbsolute(result), 'should be an absolute path');
665
+ });
666
+ // =============================================================================
667
+ // Edge cases and error handling
668
+ // =============================================================================
669
+ test('readEnvConfig handles NaN values gracefully', () => {
670
+ const saved = saveEnv();
671
+ clearEnv(Object.keys(saved));
672
+ try {
673
+ process.env.CODEVAULT_CHAT_TEMPERATURE = 'not-a-number';
674
+ const result = readEnvConfig();
675
+ // Should not set temperature if parse fails
676
+ assert.equal(result.chatLLM?.openai?.temperature, undefined);
677
+ }
678
+ finally {
679
+ restoreEnv(saved);
680
+ }
681
+ });
682
+ test('readEnvConfig handles empty string values', () => {
683
+ const saved = saveEnv();
684
+ clearEnv(Object.keys(saved));
685
+ try {
686
+ process.env.CODEVAULT_EMBEDDING_API_KEY = '';
687
+ const result = readEnvConfig();
688
+ // Empty string is falsy, so should not be set
689
+ assert.equal(result.providers, undefined);
690
+ }
691
+ finally {
692
+ restoreEnv(saved);
693
+ }
694
+ });
695
+ test('loadConfig returns object when only project config is missing', () => {
696
+ const tempDir = createTempDir();
697
+ const saved = saveEnv();
698
+ clearEnv(Object.keys(saved));
699
+ try {
700
+ const result = loadConfig(tempDir);
701
+ // Should return object without errors
702
+ // Note: global config may still populate some fields
703
+ assert.ok(typeof result === 'object');
704
+ }
705
+ finally {
706
+ restoreEnv(saved);
707
+ cleanupTempDir(tempDir);
708
+ }
709
+ });
710
+ test('readProjectConfig handles permission errors gracefully', () => {
711
+ // This test is platform-dependent and may not work on all systems
712
+ // The function should return null and log a warning on errors
713
+ const result = readProjectConfig('/nonexistent/path/that/does/not/exist');
714
+ assert.equal(result, null, 'should return null for inaccessible path');
715
+ });
716
+ //# sourceMappingURL=loader.test.js.map