@code-rag/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/auth/audit-log.d.ts +35 -0
  4. package/dist/auth/audit-log.js +110 -0
  5. package/dist/auth/audit-log.js.map +1 -0
  6. package/dist/auth/audit-log.test.d.ts +1 -0
  7. package/dist/auth/audit-log.test.js +261 -0
  8. package/dist/auth/audit-log.test.js.map +1 -0
  9. package/dist/auth/index.d.ts +6 -0
  10. package/dist/auth/index.js +5 -0
  11. package/dist/auth/index.js.map +1 -0
  12. package/dist/auth/oidc-provider.d.ts +49 -0
  13. package/dist/auth/oidc-provider.js +358 -0
  14. package/dist/auth/oidc-provider.js.map +1 -0
  15. package/dist/auth/oidc-provider.test.d.ts +1 -0
  16. package/dist/auth/oidc-provider.test.js +520 -0
  17. package/dist/auth/oidc-provider.test.js.map +1 -0
  18. package/dist/auth/rbac.d.ts +29 -0
  19. package/dist/auth/rbac.js +75 -0
  20. package/dist/auth/rbac.js.map +1 -0
  21. package/dist/auth/rbac.test.d.ts +1 -0
  22. package/dist/auth/rbac.test.js +224 -0
  23. package/dist/auth/rbac.test.js.map +1 -0
  24. package/dist/auth/saml-provider.d.ts +51 -0
  25. package/dist/auth/saml-provider.js +355 -0
  26. package/dist/auth/saml-provider.js.map +1 -0
  27. package/dist/auth/saml-provider.test.d.ts +1 -0
  28. package/dist/auth/saml-provider.test.js +422 -0
  29. package/dist/auth/saml-provider.test.js.map +1 -0
  30. package/dist/auth/types.d.ts +81 -0
  31. package/dist/auth/types.js +11 -0
  32. package/dist/auth/types.js.map +1 -0
  33. package/dist/auth/types.test.d.ts +1 -0
  34. package/dist/auth/types.test.js +147 -0
  35. package/dist/auth/types.test.js.map +1 -0
  36. package/dist/backlog/ab-reference-scanner.d.ts +10 -0
  37. package/dist/backlog/ab-reference-scanner.js +22 -0
  38. package/dist/backlog/ab-reference-scanner.js.map +1 -0
  39. package/dist/backlog/ab-reference-scanner.test.d.ts +1 -0
  40. package/dist/backlog/ab-reference-scanner.test.js +83 -0
  41. package/dist/backlog/ab-reference-scanner.test.js.map +1 -0
  42. package/dist/backlog/azure-devops-provider.d.ts +59 -0
  43. package/dist/backlog/azure-devops-provider.js +283 -0
  44. package/dist/backlog/azure-devops-provider.js.map +1 -0
  45. package/dist/backlog/backlog-provider.d.ts +13 -0
  46. package/dist/backlog/backlog-provider.js +6 -0
  47. package/dist/backlog/backlog-provider.js.map +1 -0
  48. package/dist/backlog/backlog-provider.test.d.ts +1 -0
  49. package/dist/backlog/backlog-provider.test.js +426 -0
  50. package/dist/backlog/backlog-provider.test.js.map +1 -0
  51. package/dist/backlog/clickup-provider.d.ts +55 -0
  52. package/dist/backlog/clickup-provider.js +301 -0
  53. package/dist/backlog/clickup-provider.js.map +1 -0
  54. package/dist/backlog/clickup-provider.test.d.ts +1 -0
  55. package/dist/backlog/clickup-provider.test.js +426 -0
  56. package/dist/backlog/clickup-provider.test.js.map +1 -0
  57. package/dist/backlog/clickup-reference-scanner.d.ts +10 -0
  58. package/dist/backlog/clickup-reference-scanner.js +32 -0
  59. package/dist/backlog/clickup-reference-scanner.js.map +1 -0
  60. package/dist/backlog/clickup-reference-scanner.test.d.ts +1 -0
  61. package/dist/backlog/clickup-reference-scanner.test.js +92 -0
  62. package/dist/backlog/clickup-reference-scanner.test.js.map +1 -0
  63. package/dist/backlog/code-linker.d.ts +63 -0
  64. package/dist/backlog/code-linker.js +90 -0
  65. package/dist/backlog/code-linker.js.map +1 -0
  66. package/dist/backlog/code-linker.test.d.ts +1 -0
  67. package/dist/backlog/code-linker.test.js +325 -0
  68. package/dist/backlog/code-linker.test.js.map +1 -0
  69. package/dist/backlog/index.d.ts +14 -0
  70. package/dist/backlog/index.js +8 -0
  71. package/dist/backlog/index.js.map +1 -0
  72. package/dist/backlog/jira-provider.d.ts +60 -0
  73. package/dist/backlog/jira-provider.js +272 -0
  74. package/dist/backlog/jira-provider.js.map +1 -0
  75. package/dist/backlog/jira-provider.test.d.ts +1 -0
  76. package/dist/backlog/jira-provider.test.js +449 -0
  77. package/dist/backlog/jira-provider.test.js.map +1 -0
  78. package/dist/backlog/jira-reference-scanner.d.ts +11 -0
  79. package/dist/backlog/jira-reference-scanner.js +26 -0
  80. package/dist/backlog/jira-reference-scanner.js.map +1 -0
  81. package/dist/backlog/jira-reference-scanner.test.d.ts +1 -0
  82. package/dist/backlog/jira-reference-scanner.test.js +127 -0
  83. package/dist/backlog/jira-reference-scanner.test.js.map +1 -0
  84. package/dist/backlog/types.d.ts +22 -0
  85. package/dist/backlog/types.js +1 -0
  86. package/dist/backlog/types.js.map +1 -0
  87. package/dist/chunker/ast-chunker.d.ts +45 -0
  88. package/dist/chunker/ast-chunker.js +292 -0
  89. package/dist/chunker/ast-chunker.js.map +1 -0
  90. package/dist/chunker/ast-chunker.test.d.ts +1 -0
  91. package/dist/chunker/ast-chunker.test.js +391 -0
  92. package/dist/chunker/ast-chunker.test.js.map +1 -0
  93. package/dist/chunker/chunker.d.ts +8 -0
  94. package/dist/chunker/chunker.js +1 -0
  95. package/dist/chunker/chunker.js.map +1 -0
  96. package/dist/chunker/index.d.ts +3 -0
  97. package/dist/chunker/index.js +2 -0
  98. package/dist/chunker/index.js.map +1 -0
  99. package/dist/config/config-parser.d.ts +15 -0
  100. package/dist/config/config-parser.js +283 -0
  101. package/dist/config/config-parser.js.map +1 -0
  102. package/dist/config/config-parser.test.d.ts +1 -0
  103. package/dist/config/config-parser.test.js +699 -0
  104. package/dist/config/config-parser.test.js.map +1 -0
  105. package/dist/docs/confluence-provider.d.ts +121 -0
  106. package/dist/docs/confluence-provider.js +459 -0
  107. package/dist/docs/confluence-provider.js.map +1 -0
  108. package/dist/docs/confluence-provider.test.d.ts +1 -0
  109. package/dist/docs/confluence-provider.test.js +765 -0
  110. package/dist/docs/confluence-provider.test.js.map +1 -0
  111. package/dist/docs/index.d.ts +4 -0
  112. package/dist/docs/index.js +2 -0
  113. package/dist/docs/index.js.map +1 -0
  114. package/dist/docs/sharepoint-provider.d.ts +150 -0
  115. package/dist/docs/sharepoint-provider.js +637 -0
  116. package/dist/docs/sharepoint-provider.js.map +1 -0
  117. package/dist/docs/sharepoint-provider.test.d.ts +1 -0
  118. package/dist/docs/sharepoint-provider.test.js +873 -0
  119. package/dist/docs/sharepoint-provider.test.js.map +1 -0
  120. package/dist/embedding/bm25-index.d.ts +12 -0
  121. package/dist/embedding/bm25-index.js +89 -0
  122. package/dist/embedding/bm25-index.js.map +1 -0
  123. package/dist/embedding/bm25-index.test.d.ts +1 -0
  124. package/dist/embedding/bm25-index.test.js +289 -0
  125. package/dist/embedding/bm25-index.test.js.map +1 -0
  126. package/dist/embedding/hybrid-search.d.ts +13 -0
  127. package/dist/embedding/hybrid-search.js +124 -0
  128. package/dist/embedding/hybrid-search.js.map +1 -0
  129. package/dist/embedding/hybrid-search.test.d.ts +1 -0
  130. package/dist/embedding/hybrid-search.test.js +266 -0
  131. package/dist/embedding/hybrid-search.test.js.map +1 -0
  132. package/dist/embedding/index.d.ts +11 -0
  133. package/dist/embedding/index.js +7 -0
  134. package/dist/embedding/index.js.map +1 -0
  135. package/dist/embedding/lancedb-store.d.ts +21 -0
  136. package/dist/embedding/lancedb-store.js +172 -0
  137. package/dist/embedding/lancedb-store.js.map +1 -0
  138. package/dist/embedding/lancedb-store.test.d.ts +1 -0
  139. package/dist/embedding/lancedb-store.test.js +268 -0
  140. package/dist/embedding/lancedb-store.test.js.map +1 -0
  141. package/dist/embedding/model-lifecycle-manager.d.ts +83 -0
  142. package/dist/embedding/model-lifecycle-manager.js +419 -0
  143. package/dist/embedding/model-lifecycle-manager.js.map +1 -0
  144. package/dist/embedding/model-lifecycle-manager.test.d.ts +1 -0
  145. package/dist/embedding/model-lifecycle-manager.test.js +642 -0
  146. package/dist/embedding/model-lifecycle-manager.test.js.map +1 -0
  147. package/dist/embedding/ollama-embedding-provider.d.ts +16 -0
  148. package/dist/embedding/ollama-embedding-provider.js +74 -0
  149. package/dist/embedding/ollama-embedding-provider.js.map +1 -0
  150. package/dist/embedding/ollama-embedding-provider.test.d.ts +1 -0
  151. package/dist/embedding/ollama-embedding-provider.test.js +198 -0
  152. package/dist/embedding/ollama-embedding-provider.test.js.map +1 -0
  153. package/dist/embedding/openai-compatible-embedding-provider.d.ts +19 -0
  154. package/dist/embedding/openai-compatible-embedding-provider.js +108 -0
  155. package/dist/embedding/openai-compatible-embedding-provider.js.map +1 -0
  156. package/dist/embedding/openai-compatible-embedding-provider.test.d.ts +1 -0
  157. package/dist/embedding/openai-compatible-embedding-provider.test.js +456 -0
  158. package/dist/embedding/openai-compatible-embedding-provider.test.js.map +1 -0
  159. package/dist/embedding/qdrant-store.d.ts +28 -0
  160. package/dist/embedding/qdrant-store.js +174 -0
  161. package/dist/embedding/qdrant-store.js.map +1 -0
  162. package/dist/embedding/qdrant-store.test.d.ts +1 -0
  163. package/dist/embedding/qdrant-store.test.js +359 -0
  164. package/dist/embedding/qdrant-store.test.js.map +1 -0
  165. package/dist/enrichment/index.d.ts +4 -0
  166. package/dist/enrichment/index.js +2 -0
  167. package/dist/enrichment/index.js.map +1 -0
  168. package/dist/enrichment/nl-enricher.d.ts +16 -0
  169. package/dist/enrichment/nl-enricher.js +47 -0
  170. package/dist/enrichment/nl-enricher.js.map +1 -0
  171. package/dist/enrichment/nl-enricher.test.d.ts +1 -0
  172. package/dist/enrichment/nl-enricher.test.js +154 -0
  173. package/dist/enrichment/nl-enricher.test.js.map +1 -0
  174. package/dist/enrichment/ollama-client.d.ts +18 -0
  175. package/dist/enrichment/ollama-client.js +55 -0
  176. package/dist/enrichment/ollama-client.js.map +1 -0
  177. package/dist/enrichment/ollama-client.test.d.ts +1 -0
  178. package/dist/enrichment/ollama-client.test.js +129 -0
  179. package/dist/enrichment/ollama-client.test.js.map +1 -0
  180. package/dist/git/git-client.d.ts +22 -0
  181. package/dist/git/git-client.js +6 -0
  182. package/dist/git/git-client.js.map +1 -0
  183. package/dist/git/git-client.test.d.ts +1 -0
  184. package/dist/git/git-client.test.js +200 -0
  185. package/dist/git/git-client.test.js.map +1 -0
  186. package/dist/git/ignore-filter.d.ts +2 -0
  187. package/dist/git/ignore-filter.js +31 -0
  188. package/dist/git/ignore-filter.js.map +1 -0
  189. package/dist/git/ignore-filter.test.d.ts +1 -0
  190. package/dist/git/ignore-filter.test.js +87 -0
  191. package/dist/git/ignore-filter.test.js.map +1 -0
  192. package/dist/git/index.d.ts +4 -0
  193. package/dist/git/index.js +3 -0
  194. package/dist/git/index.js.map +1 -0
  195. package/dist/git/simple-git-client.d.ts +12 -0
  196. package/dist/git/simple-git-client.js +138 -0
  197. package/dist/git/simple-git-client.js.map +1 -0
  198. package/dist/graph/cross-repo-resolver.d.ts +50 -0
  199. package/dist/graph/cross-repo-resolver.js +315 -0
  200. package/dist/graph/cross-repo-resolver.js.map +1 -0
  201. package/dist/graph/cross-repo-resolver.test.d.ts +1 -0
  202. package/dist/graph/cross-repo-resolver.test.js +548 -0
  203. package/dist/graph/cross-repo-resolver.test.js.map +1 -0
  204. package/dist/graph/dependency-graph.d.ts +44 -0
  205. package/dist/graph/dependency-graph.js +108 -0
  206. package/dist/graph/dependency-graph.js.map +1 -0
  207. package/dist/graph/dependency-graph.test.d.ts +1 -0
  208. package/dist/graph/dependency-graph.test.js +276 -0
  209. package/dist/graph/dependency-graph.test.js.map +1 -0
  210. package/dist/graph/graph-builder.d.ts +11 -0
  211. package/dist/graph/graph-builder.js +113 -0
  212. package/dist/graph/graph-builder.js.map +1 -0
  213. package/dist/graph/graph-builder.test.d.ts +1 -0
  214. package/dist/graph/graph-builder.test.js +178 -0
  215. package/dist/graph/graph-builder.test.js.map +1 -0
  216. package/dist/graph/import-resolver.d.ts +11 -0
  217. package/dist/graph/import-resolver.js +199 -0
  218. package/dist/graph/import-resolver.js.map +1 -0
  219. package/dist/graph/import-resolver.test.d.ts +1 -0
  220. package/dist/graph/import-resolver.test.js +282 -0
  221. package/dist/graph/import-resolver.test.js.map +1 -0
  222. package/dist/graph/index.d.ts +7 -0
  223. package/dist/graph/index.js +4 -0
  224. package/dist/graph/index.js.map +1 -0
  225. package/dist/index.d.ts +31 -0
  226. package/dist/index.js +15 -0
  227. package/dist/index.js.map +1 -0
  228. package/dist/indexer/file-scanner.d.ts +34 -0
  229. package/dist/indexer/file-scanner.js +69 -0
  230. package/dist/indexer/file-scanner.js.map +1 -0
  231. package/dist/indexer/file-scanner.test.d.ts +1 -0
  232. package/dist/indexer/file-scanner.test.js +110 -0
  233. package/dist/indexer/file-scanner.test.js.map +1 -0
  234. package/dist/indexer/file-watcher.d.ts +79 -0
  235. package/dist/indexer/file-watcher.js +148 -0
  236. package/dist/indexer/incremental-indexer.d.ts +67 -0
  237. package/dist/indexer/incremental-indexer.js +142 -0
  238. package/dist/indexer/incremental-indexer.js.map +1 -0
  239. package/dist/indexer/incremental-indexer.test.d.ts +1 -0
  240. package/dist/indexer/incremental-indexer.test.js +266 -0
  241. package/dist/indexer/incremental-indexer.test.js.map +1 -0
  242. package/dist/indexer/index-check.d.ts +22 -0
  243. package/dist/indexer/index-check.js +74 -0
  244. package/dist/indexer/index-check.js.map +1 -0
  245. package/dist/indexer/index-check.test.d.ts +1 -0
  246. package/dist/indexer/index-check.test.js +100 -0
  247. package/dist/indexer/index-check.test.js.map +1 -0
  248. package/dist/indexer/index-state.d.ts +61 -0
  249. package/dist/indexer/index-state.js +82 -0
  250. package/dist/indexer/index-state.js.map +1 -0
  251. package/dist/indexer/index-state.test.d.ts +1 -0
  252. package/dist/indexer/index-state.test.js +140 -0
  253. package/dist/indexer/index-state.test.js.map +1 -0
  254. package/dist/indexer/index.d.ts +12 -0
  255. package/dist/indexer/index.js +6 -0
  256. package/dist/indexer/index.js.map +1 -0
  257. package/dist/indexer/multi-repo-indexer.d.ts +63 -0
  258. package/dist/indexer/multi-repo-indexer.js +144 -0
  259. package/dist/indexer/multi-repo-indexer.js.map +1 -0
  260. package/dist/indexer/multi-repo-indexer.test.d.ts +1 -0
  261. package/dist/indexer/multi-repo-indexer.test.js +238 -0
  262. package/dist/indexer/multi-repo-indexer.test.js.map +1 -0
  263. package/dist/parser/index.d.ts +4 -0
  264. package/dist/parser/index.js +3 -0
  265. package/dist/parser/index.js.map +1 -0
  266. package/dist/parser/language-registry.d.ts +46 -0
  267. package/dist/parser/language-registry.js +219 -0
  268. package/dist/parser/language-registry.js.map +1 -0
  269. package/dist/parser/language-registry.test.d.ts +1 -0
  270. package/dist/parser/language-registry.test.js +225 -0
  271. package/dist/parser/language-registry.test.js.map +1 -0
  272. package/dist/parser/markdown-parser.d.ts +124 -0
  273. package/dist/parser/markdown-parser.js +487 -0
  274. package/dist/parser/markdown-parser.js.map +1 -0
  275. package/dist/parser/markdown-parser.test.d.ts +1 -0
  276. package/dist/parser/markdown-parser.test.js +600 -0
  277. package/dist/parser/markdown-parser.test.js.map +1 -0
  278. package/dist/parser/tree-sitter-parser.d.ts +32 -0
  279. package/dist/parser/tree-sitter-parser.js +146 -0
  280. package/dist/parser/tree-sitter-parser.js.map +1 -0
  281. package/dist/retrieval/context-expander.d.ts +51 -0
  282. package/dist/retrieval/context-expander.js +218 -0
  283. package/dist/retrieval/context-expander.js.map +1 -0
  284. package/dist/retrieval/context-expander.test.d.ts +1 -0
  285. package/dist/retrieval/context-expander.test.js +339 -0
  286. package/dist/retrieval/context-expander.test.js.map +1 -0
  287. package/dist/retrieval/cross-encoder-reranker.d.ts +16 -0
  288. package/dist/retrieval/cross-encoder-reranker.js +90 -0
  289. package/dist/retrieval/cross-encoder-reranker.js.map +1 -0
  290. package/dist/retrieval/cross-encoder-reranker.test.d.ts +1 -0
  291. package/dist/retrieval/cross-encoder-reranker.test.js +305 -0
  292. package/dist/retrieval/cross-encoder-reranker.test.js.map +1 -0
  293. package/dist/retrieval/index.d.ts +8 -0
  294. package/dist/retrieval/index.js +4 -0
  295. package/dist/retrieval/index.js.map +1 -0
  296. package/dist/retrieval/query-analyzer.d.ts +29 -0
  297. package/dist/retrieval/query-analyzer.js +238 -0
  298. package/dist/retrieval/query-analyzer.js.map +1 -0
  299. package/dist/retrieval/query-analyzer.test.d.ts +1 -0
  300. package/dist/retrieval/query-analyzer.test.js +236 -0
  301. package/dist/retrieval/query-analyzer.test.js.map +1 -0
  302. package/dist/retrieval/token-budget.d.ts +51 -0
  303. package/dist/retrieval/token-budget.js +141 -0
  304. package/dist/retrieval/token-budget.js.map +1 -0
  305. package/dist/retrieval/token-budget.test.d.ts +1 -0
  306. package/dist/retrieval/token-budget.test.js +404 -0
  307. package/dist/retrieval/token-budget.test.js.map +1 -0
  308. package/dist/storage/azure-blob-provider.d.ts +19 -0
  309. package/dist/storage/azure-blob-provider.js +199 -0
  310. package/dist/storage/azure-blob-provider.js.map +1 -0
  311. package/dist/storage/azure-blob-provider.test.d.ts +1 -0
  312. package/dist/storage/azure-blob-provider.test.js +250 -0
  313. package/dist/storage/azure-blob-provider.test.js.map +1 -0
  314. package/dist/storage/gcs-provider.d.ts +22 -0
  315. package/dist/storage/gcs-provider.js +241 -0
  316. package/dist/storage/gcs-provider.js.map +1 -0
  317. package/dist/storage/gcs-provider.test.d.ts +1 -0
  318. package/dist/storage/gcs-provider.test.js +299 -0
  319. package/dist/storage/gcs-provider.test.js.map +1 -0
  320. package/dist/storage/index.d.ts +5 -0
  321. package/dist/storage/index.js +4 -0
  322. package/dist/storage/index.js.map +1 -0
  323. package/dist/storage/s3-provider.d.ts +21 -0
  324. package/dist/storage/s3-provider.js +220 -0
  325. package/dist/storage/s3-provider.js.map +1 -0
  326. package/dist/storage/s3-provider.test.d.ts +1 -0
  327. package/dist/storage/s3-provider.test.js +329 -0
  328. package/dist/storage/s3-provider.test.js.map +1 -0
  329. package/dist/storage/types.d.ts +65 -0
  330. package/dist/storage/types.js +12 -0
  331. package/dist/storage/types.js.map +1 -0
  332. package/dist/types/chunk.d.ts +32 -0
  333. package/dist/types/chunk.js +1 -0
  334. package/dist/types/chunk.js.map +1 -0
  335. package/dist/types/config.d.ts +71 -0
  336. package/dist/types/config.js +1 -0
  337. package/dist/types/config.js.map +1 -0
  338. package/dist/types/index.d.ts +5 -0
  339. package/dist/types/index.js +1 -0
  340. package/dist/types/index.js.map +1 -0
  341. package/dist/types/provider.d.ts +54 -0
  342. package/dist/types/provider.js +36 -0
  343. package/dist/types/provider.js.map +1 -0
  344. package/dist/types/search.d.ts +27 -0
  345. package/dist/types/search.js +1 -0
  346. package/dist/types/search.js.map +1 -0
  347. package/package.json +70 -0
@@ -0,0 +1,140 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { IndexState, computeFileHash } from './index-state.js';
3
+ describe('computeFileHash', () => {
4
+ it('should produce a deterministic SHA-256 hex hash', () => {
5
+ const hash1 = computeFileHash('hello world');
6
+ const hash2 = computeFileHash('hello world');
7
+ expect(hash1).toBe(hash2);
8
+ expect(hash1).toMatch(/^[0-9a-f]{64}$/);
9
+ });
10
+ it('should produce different hashes for different content', () => {
11
+ const hash1 = computeFileHash('content A');
12
+ const hash2 = computeFileHash('content B');
13
+ expect(hash1).not.toBe(hash2);
14
+ });
15
+ it('should handle empty string', () => {
16
+ const hash = computeFileHash('');
17
+ expect(hash).toMatch(/^[0-9a-f]{64}$/);
18
+ });
19
+ });
20
+ describe('IndexState', () => {
21
+ function makeFileState(filePath, contentHash) {
22
+ return {
23
+ filePath,
24
+ contentHash,
25
+ lastIndexedAt: new Date('2025-01-15T10:00:00Z'),
26
+ chunkIds: ['chunk-1', 'chunk-2'],
27
+ };
28
+ }
29
+ describe('setFileState / getFileState', () => {
30
+ it('should store and retrieve file state', () => {
31
+ const state = new IndexState();
32
+ const fileState = makeFileState('src/main.ts', 'abc123');
33
+ state.setFileState('src/main.ts', fileState);
34
+ const retrieved = state.getFileState('src/main.ts');
35
+ expect(retrieved).toEqual(fileState);
36
+ });
37
+ it('should return undefined for unknown files', () => {
38
+ const state = new IndexState();
39
+ expect(state.getFileState('nonexistent.ts')).toBeUndefined();
40
+ });
41
+ it('should overwrite existing state', () => {
42
+ const state = new IndexState();
43
+ const original = makeFileState('src/main.ts', 'hash-1');
44
+ const updated = makeFileState('src/main.ts', 'hash-2');
45
+ state.setFileState('src/main.ts', original);
46
+ state.setFileState('src/main.ts', updated);
47
+ const retrieved = state.getFileState('src/main.ts');
48
+ expect(retrieved?.contentHash).toBe('hash-2');
49
+ });
50
+ });
51
+ describe('removeFile', () => {
52
+ it('should remove a tracked file', () => {
53
+ const state = new IndexState();
54
+ state.setFileState('src/main.ts', makeFileState('src/main.ts', 'hash'));
55
+ state.removeFile('src/main.ts');
56
+ expect(state.getFileState('src/main.ts')).toBeUndefined();
57
+ });
58
+ it('should be a no-op for untracked files', () => {
59
+ const state = new IndexState();
60
+ // Should not throw
61
+ state.removeFile('nonexistent.ts');
62
+ expect(state.getAllFiles()).toHaveLength(0);
63
+ });
64
+ });
65
+ describe('getAllFiles', () => {
66
+ it('should return all tracked file paths', () => {
67
+ const state = new IndexState();
68
+ state.setFileState('src/a.ts', makeFileState('src/a.ts', 'h1'));
69
+ state.setFileState('src/b.ts', makeFileState('src/b.ts', 'h2'));
70
+ state.setFileState('src/c.ts', makeFileState('src/c.ts', 'h3'));
71
+ const files = state.getAllFiles();
72
+ expect(files).toHaveLength(3);
73
+ expect(files).toContain('src/a.ts');
74
+ expect(files).toContain('src/b.ts');
75
+ expect(files).toContain('src/c.ts');
76
+ });
77
+ it('should return empty array when no files tracked', () => {
78
+ const state = new IndexState();
79
+ expect(state.getAllFiles()).toEqual([]);
80
+ });
81
+ });
82
+ describe('isDirty', () => {
83
+ it('should return true for a file not in the index', () => {
84
+ const state = new IndexState();
85
+ expect(state.isDirty('new-file.ts', 'some-hash')).toBe(true);
86
+ });
87
+ it('should return false when hash matches', () => {
88
+ const state = new IndexState();
89
+ state.setFileState('src/main.ts', makeFileState('src/main.ts', 'matching-hash'));
90
+ expect(state.isDirty('src/main.ts', 'matching-hash')).toBe(false);
91
+ });
92
+ it('should return true when hash differs', () => {
93
+ const state = new IndexState();
94
+ state.setFileState('src/main.ts', makeFileState('src/main.ts', 'old-hash'));
95
+ expect(state.isDirty('src/main.ts', 'new-hash')).toBe(true);
96
+ });
97
+ });
98
+ describe('toJSON / fromJSON roundtrip', () => {
99
+ it('should serialize and deserialize correctly', () => {
100
+ const state = new IndexState();
101
+ const fileState1 = makeFileState('src/a.ts', 'hash-a');
102
+ const fileState2 = {
103
+ filePath: 'src/b.ts',
104
+ contentHash: 'hash-b',
105
+ lastIndexedAt: new Date('2025-06-01T12:30:00Z'),
106
+ chunkIds: ['c1', 'c2', 'c3'],
107
+ };
108
+ state.setFileState('src/a.ts', fileState1);
109
+ state.setFileState('src/b.ts', fileState2);
110
+ const json = state.toJSON();
111
+ const restored = IndexState.fromJSON(json);
112
+ expect(restored.getAllFiles()).toHaveLength(2);
113
+ expect(restored.getFileState('src/a.ts')?.contentHash).toBe('hash-a');
114
+ expect(restored.getFileState('src/b.ts')?.contentHash).toBe('hash-b');
115
+ expect(restored.getFileState('src/b.ts')?.chunkIds).toEqual(['c1', 'c2', 'c3']);
116
+ });
117
+ it('should preserve Date objects through serialization', () => {
118
+ const state = new IndexState();
119
+ const date = new Date('2025-03-20T08:15:00Z');
120
+ state.setFileState('file.ts', {
121
+ filePath: 'file.ts',
122
+ contentHash: 'h',
123
+ lastIndexedAt: date,
124
+ chunkIds: [],
125
+ });
126
+ const json = state.toJSON();
127
+ const restored = IndexState.fromJSON(json);
128
+ const restoredState = restored.getFileState('file.ts');
129
+ expect(restoredState?.lastIndexedAt).toBeInstanceOf(Date);
130
+ expect(restoredState?.lastIndexedAt.toISOString()).toBe(date.toISOString());
131
+ });
132
+ it('should handle empty state', () => {
133
+ const state = new IndexState();
134
+ const json = state.toJSON();
135
+ const restored = IndexState.fromJSON(json);
136
+ expect(restored.getAllFiles()).toEqual([]);
137
+ });
138
+ });
139
+ });
140
+ //# sourceMappingURL=index-state.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-state.test.js","sourceRoot":"","sources":["../../src/indexer/index-state.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export type { IndexedFileState } from './index-state.js';
2
+ export { IndexState, computeFileHash } from './index-state.js';
3
+ export type { IndexCheckResult } from './index-check.js';
4
+ export { checkIndexExists } from './index-check.js';
5
+ export type { ScannedFile } from './file-scanner.js';
6
+ export { FileScanner, ScanError } from './file-scanner.js';
7
+ export type { IndexerConfig, ChangeSet, IndexerResult } from './incremental-indexer.js';
8
+ export { IncrementalIndexer, IndexerError } from './incremental-indexer.js';
9
+ export type { RepoIndexResult, MultiRepoIndexResult, MultiRepoProgressCallback, MultiRepoIndexOptions, RepoProcessor, } from './multi-repo-indexer.js';
10
+ export { MultiRepoIndexer, MultiRepoIndexerError } from './multi-repo-indexer.js';
11
+ export type { FileWatcherConfig, FileWatcherEvents } from './file-watcher.js';
12
+ export { FileWatcher } from './file-watcher.js';
@@ -0,0 +1,6 @@
1
+ export { IndexState, computeFileHash } from './index-state.js';
2
+ export { checkIndexExists } from './index-check.js';
3
+ export { FileScanner, ScanError } from './file-scanner.js';
4
+ export { IncrementalIndexer, IndexerError } from './incremental-indexer.js';
5
+ export { MultiRepoIndexer, MultiRepoIndexerError } from './multi-repo-indexer.js';
6
+ export { FileWatcher } from './file-watcher.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/indexer/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAS5E,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { type Result } from 'neverthrow';
2
+ import type { RepoConfig } from '../types/config.js';
3
+ import type { ScannedFile } from './file-scanner.js';
4
+ import { IndexState } from './index-state.js';
5
+ /**
6
+ * Result for a single repo's indexing run.
7
+ */
8
+ export interface RepoIndexResult {
9
+ repoName: string;
10
+ filesProcessed: number;
11
+ chunksCreated: number;
12
+ errors: string[];
13
+ }
14
+ /**
15
+ * Aggregated result for multi-repo indexing.
16
+ */
17
+ export interface MultiRepoIndexResult {
18
+ repoResults: RepoIndexResult[];
19
+ }
20
+ /**
21
+ * Progress callback for per-repo progress reporting.
22
+ */
23
+ export type MultiRepoProgressCallback = (repoName: string, status: string) => void;
24
+ /**
25
+ * Error type for multi-repo indexer operations.
26
+ */
27
+ export declare class MultiRepoIndexerError extends Error {
28
+ constructor(message: string);
29
+ }
30
+ /**
31
+ * Options for a multi-repo indexing run.
32
+ */
33
+ export interface MultiRepoIndexOptions {
34
+ full?: boolean;
35
+ onProgress?: MultiRepoProgressCallback;
36
+ }
37
+ /**
38
+ * Callback that processes a single repo's files and returns chunk count.
39
+ * This allows the CLI or other consumers to plug in their own
40
+ * parse/chunk/embed/store pipeline for each repo.
41
+ */
42
+ export type RepoProcessor = (repoName: string, repoPath: string, files: ScannedFile[], indexState: IndexState, storagePath: string) => Promise<Result<number, Error>>;
43
+ /**
44
+ * Orchestrates indexing across multiple configured repos.
45
+ *
46
+ * Each repo gets its own IndexState stored under `{storagePath}/{repoName}/index-state.json`,
47
+ * enabling independent incremental indexing per repo. Chunk metadata is stamped
48
+ * with the repo name for cross-repo identification during search.
49
+ */
50
+ export declare class MultiRepoIndexer {
51
+ private readonly repos;
52
+ private readonly storagePath;
53
+ constructor(repos: RepoConfig[], storagePath: string);
54
+ /**
55
+ * Index all configured repos. Errors in one repo do not stop others.
56
+ */
57
+ indexAll(options?: MultiRepoIndexOptions, processor?: RepoProcessor): Promise<Result<MultiRepoIndexResult, MultiRepoIndexerError>>;
58
+ /**
59
+ * Resolve a display/storage name for a repo.
60
+ * Uses the explicit `name` field if set, otherwise derives from the path.
61
+ */
62
+ private resolveRepoName;
63
+ }
@@ -0,0 +1,144 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join, basename } from 'node:path';
3
+ import { ok } from 'neverthrow';
4
+ import { FileScanner } from './file-scanner.js';
5
+ import { IndexState } from './index-state.js';
6
+ import { createIgnoreFilter } from '../git/ignore-filter.js';
7
+ /**
8
+ * Error type for multi-repo indexer operations.
9
+ */
10
+ export class MultiRepoIndexerError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = 'MultiRepoIndexerError';
14
+ }
15
+ }
16
+ /**
17
+ * Orchestrates indexing across multiple configured repos.
18
+ *
19
+ * Each repo gets its own IndexState stored under `{storagePath}/{repoName}/index-state.json`,
20
+ * enabling independent incremental indexing per repo. Chunk metadata is stamped
21
+ * with the repo name for cross-repo identification during search.
22
+ */
23
+ export class MultiRepoIndexer {
24
+ repos;
25
+ storagePath;
26
+ constructor(repos, storagePath) {
27
+ this.repos = repos;
28
+ this.storagePath = storagePath;
29
+ }
30
+ /**
31
+ * Index all configured repos. Errors in one repo do not stop others.
32
+ */
33
+ async indexAll(options = {}, processor) {
34
+ const { full = false, onProgress } = options;
35
+ const repoResults = [];
36
+ for (const repo of this.repos) {
37
+ const repoName = this.resolveRepoName(repo);
38
+ try {
39
+ onProgress?.(repoName, 'Starting...');
40
+ // Ensure per-repo storage directory exists
41
+ const repoStoragePath = join(this.storagePath, repoName);
42
+ await mkdir(repoStoragePath, { recursive: true });
43
+ // Load or create per-repo index state
44
+ let indexState = new IndexState();
45
+ const indexStatePath = join(repoStoragePath, 'index-state.json');
46
+ if (!full) {
47
+ try {
48
+ const stateData = await readFile(indexStatePath, 'utf-8');
49
+ indexState = IndexState.fromJSON(JSON.parse(stateData));
50
+ }
51
+ catch {
52
+ // No saved state, start fresh
53
+ }
54
+ }
55
+ // Scan files
56
+ onProgress?.(repoName, 'Scanning files...');
57
+ const ignoreFilter = createIgnoreFilter(repo.path);
58
+ const scanner = new FileScanner(repo.path, ignoreFilter);
59
+ const scanResult = await scanner.scanFiles();
60
+ if (scanResult.isErr()) {
61
+ repoResults.push({
62
+ repoName,
63
+ filesProcessed: 0,
64
+ chunksCreated: 0,
65
+ errors: [`Scan failed: ${scanResult.error.message}`],
66
+ });
67
+ onProgress?.(repoName, `Failed: ${scanResult.error.message}`);
68
+ continue;
69
+ }
70
+ const scannedFiles = scanResult.value;
71
+ // Filter to changed files (incremental)
72
+ let filesToProcess = scannedFiles;
73
+ if (!full) {
74
+ filesToProcess = scannedFiles.filter((f) => indexState.isDirty(f.filePath, f.contentHash));
75
+ }
76
+ onProgress?.(repoName, `Processing ${filesToProcess.length} file(s)...`);
77
+ let chunksCreated = 0;
78
+ if (processor && filesToProcess.length > 0) {
79
+ // Delegate to consumer-provided processor
80
+ const processResult = await processor(repoName, repo.path, filesToProcess, indexState, repoStoragePath);
81
+ if (processResult.isErr()) {
82
+ repoResults.push({
83
+ repoName,
84
+ filesProcessed: filesToProcess.length,
85
+ chunksCreated: 0,
86
+ errors: [processResult.error.message],
87
+ });
88
+ onProgress?.(repoName, `Failed: ${processResult.error.message}`);
89
+ continue;
90
+ }
91
+ chunksCreated = processResult.value;
92
+ }
93
+ else {
94
+ // Default: update index state with file tracking (no chunking)
95
+ for (const file of filesToProcess) {
96
+ indexState.setFileState(file.filePath, {
97
+ filePath: file.filePath,
98
+ contentHash: file.contentHash,
99
+ lastIndexedAt: new Date(),
100
+ chunkIds: [],
101
+ });
102
+ }
103
+ }
104
+ // Detect and remove deleted files
105
+ const currentFilePaths = new Set(scannedFiles.map((f) => f.filePath));
106
+ for (const trackedPath of indexState.getAllFiles()) {
107
+ if (!currentFilePaths.has(trackedPath)) {
108
+ indexState.removeFile(trackedPath);
109
+ }
110
+ }
111
+ // Persist index state
112
+ await writeFile(indexStatePath, JSON.stringify(indexState.toJSON(), null, 2), 'utf-8');
113
+ repoResults.push({
114
+ repoName,
115
+ filesProcessed: filesToProcess.length,
116
+ chunksCreated,
117
+ errors: [],
118
+ });
119
+ onProgress?.(repoName, 'Done');
120
+ }
121
+ catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ repoResults.push({
124
+ repoName,
125
+ filesProcessed: 0,
126
+ chunksCreated: 0,
127
+ errors: [message],
128
+ });
129
+ onProgress?.(repoName, `Failed: ${message}`);
130
+ }
131
+ }
132
+ return ok({ repoResults });
133
+ }
134
+ /**
135
+ * Resolve a display/storage name for a repo.
136
+ * Uses the explicit `name` field if set, otherwise derives from the path.
137
+ */
138
+ resolveRepoName(repo) {
139
+ if (repo.name) {
140
+ return repo.name;
141
+ }
142
+ return basename(repo.path);
143
+ }
144
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-repo-indexer.js","sourceRoot":"","sources":["../../src/indexer/multi-repo-indexer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,238 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { ok, err } from 'neverthrow';
6
+ import { MultiRepoIndexer } from './multi-repo-indexer.js';
7
+ import { IndexState } from './index-state.js';
8
+ describe('MultiRepoIndexer', () => {
9
+ let tempDir;
10
+ let storagePath;
11
+ beforeEach(() => {
12
+ tempDir = mkdtempSync(join(tmpdir(), 'coderag-multi-repo-test-'));
13
+ storagePath = join(tempDir, '.coderag');
14
+ mkdirSync(storagePath, { recursive: true });
15
+ });
16
+ afterEach(() => {
17
+ rmSync(tempDir, { recursive: true, force: true });
18
+ });
19
+ function createRepoDir(name, files) {
20
+ const repoDir = join(tempDir, name);
21
+ mkdirSync(repoDir, { recursive: true });
22
+ for (const [filePath, content] of Object.entries(files)) {
23
+ const fullPath = join(repoDir, filePath);
24
+ const dir = fullPath.substring(0, fullPath.lastIndexOf('/'));
25
+ if (dir !== repoDir) {
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+ writeFileSync(fullPath, content);
29
+ }
30
+ return repoDir;
31
+ }
32
+ describe('indexAll', () => {
33
+ it('should handle single repo (backwards compatible)', async () => {
34
+ const repoDir = createRepoDir('my-repo', {
35
+ 'main.ts': 'export const x = 1;',
36
+ });
37
+ const repos = [{ path: repoDir, name: 'my-repo' }];
38
+ const indexer = new MultiRepoIndexer(repos, storagePath);
39
+ const result = await indexer.indexAll();
40
+ expect(result.isOk()).toBe(true);
41
+ if (result.isOk()) {
42
+ expect(result.value.repoResults).toHaveLength(1);
43
+ expect(result.value.repoResults[0]?.repoName).toBe('my-repo');
44
+ expect(result.value.repoResults[0]?.filesProcessed).toBe(1);
45
+ expect(result.value.repoResults[0]?.errors).toEqual([]);
46
+ }
47
+ });
48
+ it('should iterate multiple repos', async () => {
49
+ const repo1Dir = createRepoDir('repo-a', {
50
+ 'a.ts': 'export const a = 1;',
51
+ 'b.ts': 'export const b = 2;',
52
+ });
53
+ const repo2Dir = createRepoDir('repo-b', {
54
+ 'c.ts': 'export const c = 3;',
55
+ });
56
+ const repos = [
57
+ { path: repo1Dir, name: 'repo-a' },
58
+ { path: repo2Dir, name: 'repo-b' },
59
+ ];
60
+ const indexer = new MultiRepoIndexer(repos, storagePath);
61
+ const result = await indexer.indexAll();
62
+ expect(result.isOk()).toBe(true);
63
+ if (result.isOk()) {
64
+ expect(result.value.repoResults).toHaveLength(2);
65
+ const repoA = result.value.repoResults.find((r) => r.repoName === 'repo-a');
66
+ const repoB = result.value.repoResults.find((r) => r.repoName === 'repo-b');
67
+ expect(repoA).toBeDefined();
68
+ expect(repoA?.filesProcessed).toBe(2);
69
+ expect(repoA?.errors).toEqual([]);
70
+ expect(repoB).toBeDefined();
71
+ expect(repoB?.filesProcessed).toBe(1);
72
+ expect(repoB?.errors).toEqual([]);
73
+ }
74
+ });
75
+ it('should store per-repo independent index state', async () => {
76
+ const repo1Dir = createRepoDir('repo-one', {
77
+ 'file1.ts': 'const one = 1;',
78
+ });
79
+ const repo2Dir = createRepoDir('repo-two', {
80
+ 'file2.ts': 'const two = 2;',
81
+ });
82
+ const repos = [
83
+ { path: repo1Dir, name: 'repo-one' },
84
+ { path: repo2Dir, name: 'repo-two' },
85
+ ];
86
+ const indexer = new MultiRepoIndexer(repos, storagePath);
87
+ const result = await indexer.indexAll();
88
+ expect(result.isOk()).toBe(true);
89
+ // Verify separate index-state.json files exist
90
+ const state1Path = join(storagePath, 'repo-one', 'index-state.json');
91
+ const state2Path = join(storagePath, 'repo-two', 'index-state.json');
92
+ expect(existsSync(state1Path)).toBe(true);
93
+ expect(existsSync(state2Path)).toBe(true);
94
+ // Verify they contain the correct files
95
+ const state1 = IndexState.fromJSON(JSON.parse(readFileSync(state1Path, 'utf-8')));
96
+ const state2 = IndexState.fromJSON(JSON.parse(readFileSync(state2Path, 'utf-8')));
97
+ expect(state1.getAllFiles()).toContain('file1.ts');
98
+ expect(state1.getFileState('file2.ts')).toBeUndefined();
99
+ expect(state2.getAllFiles()).toContain('file2.ts');
100
+ expect(state2.getFileState('file1.ts')).toBeUndefined();
101
+ });
102
+ it('should pass repoName to processor for setting in chunk metadata', async () => {
103
+ const repoDir = createRepoDir('test-repo', {
104
+ 'app.ts': 'export function main() {}',
105
+ });
106
+ const repos = [{ path: repoDir, name: 'test-repo' }];
107
+ const indexer = new MultiRepoIndexer(repos, storagePath);
108
+ const receivedRepoNames = [];
109
+ const processor = async (repoName, _repoPath, _files, _state, _storage) => {
110
+ receivedRepoNames.push(repoName);
111
+ return ok(5); // 5 chunks created
112
+ };
113
+ const result = await indexer.indexAll({}, processor);
114
+ expect(result.isOk()).toBe(true);
115
+ expect(receivedRepoNames).toEqual(['test-repo']);
116
+ if (result.isOk()) {
117
+ expect(result.value.repoResults[0]?.chunksCreated).toBe(5);
118
+ }
119
+ });
120
+ it('should not stop on error in one repo', async () => {
121
+ const repo1Dir = createRepoDir('good-repo', {
122
+ 'ok.ts': 'export const ok = true;',
123
+ });
124
+ // Use a non-existent path to cause an error
125
+ const badRepoPath = join(tempDir, 'non-existent-repo');
126
+ const repos = [
127
+ { path: badRepoPath, name: 'bad-repo' },
128
+ { path: repo1Dir, name: 'good-repo' },
129
+ ];
130
+ const indexer = new MultiRepoIndexer(repos, storagePath);
131
+ const result = await indexer.indexAll();
132
+ expect(result.isOk()).toBe(true);
133
+ if (result.isOk()) {
134
+ expect(result.value.repoResults).toHaveLength(2);
135
+ const badRepo = result.value.repoResults.find((r) => r.repoName === 'bad-repo');
136
+ const goodRepo = result.value.repoResults.find((r) => r.repoName === 'good-repo');
137
+ expect(badRepo).toBeDefined();
138
+ expect(badRepo?.errors.length).toBeGreaterThan(0);
139
+ expect(goodRepo).toBeDefined();
140
+ expect(goodRepo?.filesProcessed).toBe(1);
141
+ expect(goodRepo?.errors).toEqual([]);
142
+ }
143
+ });
144
+ it('should handle empty repos array', async () => {
145
+ const repos = [];
146
+ const indexer = new MultiRepoIndexer(repos, storagePath);
147
+ const result = await indexer.indexAll();
148
+ expect(result.isOk()).toBe(true);
149
+ if (result.isOk()) {
150
+ expect(result.value.repoResults).toEqual([]);
151
+ }
152
+ });
153
+ it('should derive repo name from path when name is not set', async () => {
154
+ const repoDir = createRepoDir('derived-name', {
155
+ 'x.ts': 'const x = 1;',
156
+ });
157
+ const repos = [{ path: repoDir }];
158
+ const indexer = new MultiRepoIndexer(repos, storagePath);
159
+ const result = await indexer.indexAll();
160
+ expect(result.isOk()).toBe(true);
161
+ if (result.isOk()) {
162
+ expect(result.value.repoResults[0]?.repoName).toBe('derived-name');
163
+ }
164
+ });
165
+ it('should support incremental indexing per repo independently', async () => {
166
+ const repo1Dir = createRepoDir('incr-repo', {
167
+ 'stable.ts': 'const stable = true;',
168
+ });
169
+ const repos = [{ path: repo1Dir, name: 'incr-repo' }];
170
+ const indexer = new MultiRepoIndexer(repos, storagePath);
171
+ // First indexing run
172
+ const firstResult = await indexer.indexAll();
173
+ expect(firstResult.isOk()).toBe(true);
174
+ if (firstResult.isOk()) {
175
+ expect(firstResult.value.repoResults[0]?.filesProcessed).toBe(1);
176
+ }
177
+ // Second indexing run without changes - should process 0 files (incremental)
178
+ const secondResult = await indexer.indexAll();
179
+ expect(secondResult.isOk()).toBe(true);
180
+ if (secondResult.isOk()) {
181
+ expect(secondResult.value.repoResults[0]?.filesProcessed).toBe(0);
182
+ }
183
+ // Modify a file and re-index - should detect change
184
+ writeFileSync(join(repo1Dir, 'stable.ts'), 'const stable = false;');
185
+ const thirdResult = await indexer.indexAll();
186
+ expect(thirdResult.isOk()).toBe(true);
187
+ if (thirdResult.isOk()) {
188
+ expect(thirdResult.value.repoResults[0]?.filesProcessed).toBe(1);
189
+ }
190
+ });
191
+ it('should report per-repo progress via callback', async () => {
192
+ const repoDir = createRepoDir('progress-repo', {
193
+ 'p.ts': 'const p = 1;',
194
+ });
195
+ const repos = [{ path: repoDir, name: 'progress-repo' }];
196
+ const indexer = new MultiRepoIndexer(repos, storagePath);
197
+ const progressUpdates = [];
198
+ const onProgress = (repoName, status) => {
199
+ progressUpdates.push({ repoName, status });
200
+ };
201
+ await indexer.indexAll({ onProgress });
202
+ expect(progressUpdates.length).toBeGreaterThan(0);
203
+ expect(progressUpdates[0]?.repoName).toBe('progress-repo');
204
+ expect(progressUpdates.some((p) => p.status === 'Done')).toBe(true);
205
+ });
206
+ it('should support full reindex ignoring prior state', async () => {
207
+ const repoDir = createRepoDir('full-repo', {
208
+ 'f.ts': 'const f = 1;',
209
+ });
210
+ const repos = [{ path: repoDir, name: 'full-repo' }];
211
+ const indexer = new MultiRepoIndexer(repos, storagePath);
212
+ // First run
213
+ await indexer.indexAll();
214
+ // Full re-index should process all files again
215
+ const fullResult = await indexer.indexAll({ full: true });
216
+ expect(fullResult.isOk()).toBe(true);
217
+ if (fullResult.isOk()) {
218
+ expect(fullResult.value.repoResults[0]?.filesProcessed).toBe(1);
219
+ }
220
+ });
221
+ it('should handle processor returning error', async () => {
222
+ const repoDir = createRepoDir('err-repo', {
223
+ 'e.ts': 'const e = 1;',
224
+ });
225
+ const repos = [{ path: repoDir, name: 'err-repo' }];
226
+ const indexer = new MultiRepoIndexer(repos, storagePath);
227
+ const processor = async () => {
228
+ return err(new Error('Processing failed'));
229
+ };
230
+ const result = await indexer.indexAll({}, processor);
231
+ expect(result.isOk()).toBe(true);
232
+ if (result.isOk()) {
233
+ expect(result.value.repoResults[0]?.errors).toContain('Processing failed');
234
+ }
235
+ });
236
+ });
237
+ });
238
+ //# sourceMappingURL=multi-repo-indexer.test.js.map