@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 @@
1
+ export {};
@@ -0,0 +1,154 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { ok, err } from 'neverthrow';
3
+ import { OllamaError } from './ollama-client.js';
4
+ import { NLEnricher, EnrichmentError } from './nl-enricher.js';
5
+ function makeChunk(overrides = {}) {
6
+ return {
7
+ id: 'chunk-1',
8
+ content: 'function add(a: number, b: number): number { return a + b; }',
9
+ nlSummary: '',
10
+ filePath: '/src/math.ts',
11
+ startLine: 1,
12
+ endLine: 1,
13
+ language: 'typescript',
14
+ metadata: {
15
+ chunkType: 'function',
16
+ name: 'add',
17
+ declarations: ['add'],
18
+ imports: [],
19
+ exports: ['add'],
20
+ },
21
+ ...overrides,
22
+ };
23
+ }
24
+ function createMockClient(generateFn) {
25
+ return { generate: generateFn };
26
+ }
27
+ describe('NLEnricher', () => {
28
+ describe('enrichChunk', () => {
29
+ it('should add nlSummary to the chunk on success', async () => {
30
+ const mockClient = createMockClient(vi.fn().mockResolvedValue(ok('Adds two numbers together.')));
31
+ const enricher = new NLEnricher(mockClient);
32
+ const result = await enricher.enrichChunk(makeChunk());
33
+ expect(result.isOk()).toBe(true);
34
+ if (result.isOk()) {
35
+ expect(result.value.nlSummary).toBe('Adds two numbers together.');
36
+ expect(result.value.id).toBe('chunk-1');
37
+ expect(result.value.content).toBe('function add(a: number, b: number): number { return a + b; }');
38
+ }
39
+ });
40
+ it('should trim whitespace from the summary', async () => {
41
+ const mockClient = createMockClient(vi.fn().mockResolvedValue(ok(' Adds two numbers. \n')));
42
+ const enricher = new NLEnricher(mockClient);
43
+ const result = await enricher.enrichChunk(makeChunk());
44
+ expect(result.isOk()).toBe(true);
45
+ if (result.isOk()) {
46
+ expect(result.value.nlSummary).toBe('Adds two numbers.');
47
+ }
48
+ });
49
+ it('should build the correct prompt with the chunk language', async () => {
50
+ const generateFn = vi.fn().mockResolvedValue(ok('Summary.'));
51
+ const mockClient = createMockClient(generateFn);
52
+ const enricher = new NLEnricher(mockClient);
53
+ const chunk = makeChunk({ language: 'python' });
54
+ await enricher.enrichChunk(chunk);
55
+ expect(generateFn).toHaveBeenCalledWith(expect.stringContaining('Summarize this python code'));
56
+ expect(generateFn).toHaveBeenCalledWith(expect.stringContaining(chunk.content));
57
+ });
58
+ it('should return EnrichmentError when Ollama fails', async () => {
59
+ const mockClient = createMockClient(vi.fn().mockResolvedValue(err(new OllamaError('Connection refused'))));
60
+ const enricher = new NLEnricher(mockClient);
61
+ const result = await enricher.enrichChunk(makeChunk());
62
+ expect(result.isErr()).toBe(true);
63
+ if (result.isErr()) {
64
+ expect(result.error).toBeInstanceOf(EnrichmentError);
65
+ expect(result.error.message).toContain('chunk-1');
66
+ expect(result.error.message).toContain('Connection refused');
67
+ }
68
+ });
69
+ });
70
+ describe('enrichBatch', () => {
71
+ it('should process all chunks successfully', async () => {
72
+ let callCount = 0;
73
+ const mockClient = createMockClient(vi.fn().mockImplementation(() => {
74
+ callCount++;
75
+ return Promise.resolve(ok(`Summary ${callCount}.`));
76
+ }));
77
+ const enricher = new NLEnricher(mockClient);
78
+ const chunks = [
79
+ makeChunk({ id: 'c1' }),
80
+ makeChunk({ id: 'c2' }),
81
+ makeChunk({ id: 'c3' }),
82
+ ];
83
+ const result = await enricher.enrichBatch(chunks);
84
+ expect(result.isOk()).toBe(true);
85
+ if (result.isOk()) {
86
+ expect(result.value.enriched).toHaveLength(3);
87
+ expect(result.value.failedCount).toBe(0);
88
+ expect(result.value.enriched[0].nlSummary).toBe('Summary 1.');
89
+ expect(result.value.enriched[1].nlSummary).toBe('Summary 2.');
90
+ expect(result.value.enriched[2].nlSummary).toBe('Summary 3.');
91
+ }
92
+ });
93
+ it('should respect concurrency limit', async () => {
94
+ let maxConcurrent = 0;
95
+ let currentConcurrent = 0;
96
+ const mockClient = createMockClient(vi.fn().mockImplementation(() => {
97
+ currentConcurrent++;
98
+ if (currentConcurrent > maxConcurrent) {
99
+ maxConcurrent = currentConcurrent;
100
+ }
101
+ return new Promise((resolve) => {
102
+ setTimeout(() => {
103
+ currentConcurrent--;
104
+ resolve(ok('Summary.'));
105
+ }, 10);
106
+ });
107
+ }));
108
+ const enricher = new NLEnricher(mockClient);
109
+ const chunks = Array.from({ length: 6 }, (_, i) => makeChunk({ id: `c${i}` }));
110
+ const result = await enricher.enrichBatch(chunks, 2);
111
+ expect(result.isOk()).toBe(true);
112
+ if (result.isOk()) {
113
+ expect(result.value.enriched).toHaveLength(6);
114
+ expect(result.value.failedCount).toBe(0);
115
+ }
116
+ expect(maxConcurrent).toBeLessThanOrEqual(2);
117
+ });
118
+ it('should return ok with empty array when input is empty', async () => {
119
+ const mockClient = createMockClient(vi.fn());
120
+ const enricher = new NLEnricher(mockClient);
121
+ const result = await enricher.enrichBatch([]);
122
+ expect(result.isOk()).toBe(true);
123
+ if (result.isOk()) {
124
+ expect(result.value.enriched).toEqual([]);
125
+ expect(result.value.failedCount).toBe(0);
126
+ }
127
+ });
128
+ it('should return partial results when some chunks fail', async () => {
129
+ let callCount = 0;
130
+ const mockClient = createMockClient(vi.fn().mockImplementation(() => {
131
+ callCount++;
132
+ if (callCount === 2) {
133
+ return Promise.resolve(err(new OllamaError('Model not loaded')));
134
+ }
135
+ return Promise.resolve(ok('Summary.'));
136
+ }));
137
+ const enricher = new NLEnricher(mockClient);
138
+ const chunks = [
139
+ makeChunk({ id: 'c1' }),
140
+ makeChunk({ id: 'c2' }),
141
+ makeChunk({ id: 'c3' }),
142
+ ];
143
+ const result = await enricher.enrichBatch(chunks);
144
+ expect(result.isOk()).toBe(true);
145
+ if (result.isOk()) {
146
+ expect(result.value.enriched).toHaveLength(2);
147
+ expect(result.value.failedCount).toBe(1);
148
+ expect(result.value.enriched[0].id).toBe('c1');
149
+ expect(result.value.enriched[1].id).toBe('c3');
150
+ }
151
+ });
152
+ });
153
+ });
154
+ //# sourceMappingURL=nl-enricher.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nl-enricher.test.js","sourceRoot":"","sources":["../../src/enrichment/nl-enricher.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type Result } from 'neverthrow';
2
+ export interface OllamaConfig {
3
+ baseUrl: string;
4
+ model: string;
5
+ timeout: number;
6
+ /** Maximum tokens to generate per request (Ollama num_predict). 0 = unlimited. */
7
+ maxTokens: number;
8
+ }
9
+ export declare class OllamaError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export declare class OllamaClient {
13
+ private readonly config;
14
+ constructor(config?: Partial<OllamaConfig>);
15
+ get currentConfig(): OllamaConfig;
16
+ generate(prompt: string): Promise<Result<string, OllamaError>>;
17
+ isAvailable(): Promise<boolean>;
18
+ }
@@ -0,0 +1,55 @@
1
+ import { ok, err } from 'neverthrow';
2
+ const DEFAULT_CONFIG = {
3
+ baseUrl: 'http://localhost:11434',
4
+ model: 'qwen2.5-coder:7b',
5
+ timeout: 60_000,
6
+ maxTokens: 100,
7
+ };
8
+ export class OllamaError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'OllamaError';
12
+ }
13
+ }
14
+ export class OllamaClient {
15
+ config;
16
+ constructor(config) {
17
+ this.config = { ...DEFAULT_CONFIG, ...config };
18
+ }
19
+ get currentConfig() {
20
+ return { ...this.config };
21
+ }
22
+ async generate(prompt) {
23
+ try {
24
+ const response = await globalThis.fetch(`${this.config.baseUrl}/api/generate`, {
25
+ method: 'POST',
26
+ headers: { 'Content-Type': 'application/json' },
27
+ body: JSON.stringify({
28
+ model: this.config.model,
29
+ prompt,
30
+ stream: false,
31
+ ...(this.config.maxTokens > 0 ? { options: { num_predict: this.config.maxTokens } } : {}),
32
+ }),
33
+ signal: AbortSignal.timeout(this.config.timeout),
34
+ });
35
+ if (!response.ok) {
36
+ return err(new OllamaError(`Ollama API returned status ${response.status}: ${response.statusText}`));
37
+ }
38
+ const data = (await response.json());
39
+ return ok(data.response);
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : 'Unknown error';
43
+ return err(new OllamaError(`Ollama request failed: ${message}`));
44
+ }
45
+ }
46
+ async isAvailable() {
47
+ try {
48
+ const response = await globalThis.fetch(`${this.config.baseUrl}/api/tags`, { signal: AbortSignal.timeout(this.config.timeout) });
49
+ return response.ok;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama-client.js","sourceRoot":"","sources":["../../src/enrichment/ollama-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAe,MAAM,YAAY,CAAC;AAUlD,MAAM,cAAc,GAAiB;IACnC,OAAO,EAAE,wBAAwB;IACjC,KAAK,EAAE,kBAAkB;IACzB,OAAO,EAAE,MAAM;IACf,SAAS,EAAE,GAAG;CACf,CAAC;AAEF,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAMD,MAAM,OAAO,YAAY;IACN,MAAM,CAAe;IAEtC,YAAY,MAA8B;QACxC,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;IACjD,CAAC;IAED,IAAI,aAAa;QACf,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAc;QAC3B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CACrC,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,eAAe,EACrC;gBACE,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;oBACxB,MAAM;oBACN,MAAM,EAAE,KAAK;oBACb,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC1F,CAAC;gBACF,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;aACjD,CACF,CAAC;YAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,GAAG,CACR,IAAI,WAAW,CACb,8BAA8B,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,CACxE,CACF,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAC;YAC/D,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YAC3D,OAAO,GAAG,CAAC,IAAI,WAAW,CAAC,0BAA0B,OAAO,EAAE,CAAC,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CACrC,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,WAAW,EACjC,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CACrD,CAAC;YACF,OAAO,QAAQ,CAAC,EAAE,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { OllamaClient, OllamaError } from './ollama-client.js';
3
+ describe('OllamaClient', () => {
4
+ const originalFetch = globalThis.fetch;
5
+ beforeEach(() => {
6
+ globalThis.fetch = vi.fn();
7
+ });
8
+ afterEach(() => {
9
+ globalThis.fetch = originalFetch;
10
+ });
11
+ describe('constructor / config', () => {
12
+ it('should use default config values when none provided', () => {
13
+ const client = new OllamaClient();
14
+ const config = client.currentConfig;
15
+ expect(config.baseUrl).toBe('http://localhost:11434');
16
+ expect(config.model).toBe('qwen2.5-coder:7b');
17
+ expect(config.timeout).toBe(60_000);
18
+ expect(config.maxTokens).toBe(100);
19
+ });
20
+ it('should accept custom config values', () => {
21
+ const client = new OllamaClient({
22
+ baseUrl: 'http://remote:9999',
23
+ model: 'llama3.2',
24
+ timeout: 60_000,
25
+ maxTokens: 200,
26
+ });
27
+ const config = client.currentConfig;
28
+ expect(config.baseUrl).toBe('http://remote:9999');
29
+ expect(config.model).toBe('llama3.2');
30
+ expect(config.timeout).toBe(60_000);
31
+ expect(config.maxTokens).toBe(200);
32
+ });
33
+ it('should merge partial config with defaults', () => {
34
+ const client = new OllamaClient({ model: 'codellama' });
35
+ const config = client.currentConfig;
36
+ expect(config.baseUrl).toBe('http://localhost:11434');
37
+ expect(config.model).toBe('codellama');
38
+ expect(config.timeout).toBe(60_000);
39
+ });
40
+ });
41
+ describe('generate', () => {
42
+ it('should return the response on success', async () => {
43
+ const mockResponse = {
44
+ ok: true,
45
+ status: 200,
46
+ statusText: 'OK',
47
+ json: vi.fn().mockResolvedValue({ response: 'This function adds two numbers.' }),
48
+ };
49
+ vi.mocked(globalThis.fetch).mockResolvedValue(mockResponse);
50
+ const client = new OllamaClient();
51
+ const result = await client.generate('Summarize this code');
52
+ expect(result.isOk()).toBe(true);
53
+ if (result.isOk()) {
54
+ expect(result.value).toBe('This function adds two numbers.');
55
+ }
56
+ expect(globalThis.fetch).toHaveBeenCalledWith('http://localhost:11434/api/generate', expect.objectContaining({
57
+ method: 'POST',
58
+ headers: { 'Content-Type': 'application/json' },
59
+ body: JSON.stringify({
60
+ model: 'qwen2.5-coder:7b',
61
+ prompt: 'Summarize this code',
62
+ stream: false,
63
+ options: { num_predict: 100 },
64
+ }),
65
+ }));
66
+ });
67
+ it('should return OllamaError on non-200 status', async () => {
68
+ const mockResponse = {
69
+ ok: false,
70
+ status: 500,
71
+ statusText: 'Internal Server Error',
72
+ };
73
+ vi.mocked(globalThis.fetch).mockResolvedValue(mockResponse);
74
+ const client = new OllamaClient();
75
+ const result = await client.generate('Summarize this code');
76
+ expect(result.isErr()).toBe(true);
77
+ if (result.isErr()) {
78
+ expect(result.error).toBeInstanceOf(OllamaError);
79
+ expect(result.error.message).toContain('status 500');
80
+ expect(result.error.message).toContain('Internal Server Error');
81
+ }
82
+ });
83
+ it('should return OllamaError on network error', async () => {
84
+ vi.mocked(globalThis.fetch).mockRejectedValue(new Error('ECONNREFUSED'));
85
+ const client = new OllamaClient();
86
+ const result = await client.generate('Summarize this code');
87
+ expect(result.isErr()).toBe(true);
88
+ if (result.isErr()) {
89
+ expect(result.error).toBeInstanceOf(OllamaError);
90
+ expect(result.error.message).toContain('Ollama request failed');
91
+ expect(result.error.message).toContain('ECONNREFUSED');
92
+ }
93
+ });
94
+ it('should handle non-Error throw gracefully', async () => {
95
+ vi.mocked(globalThis.fetch).mockRejectedValue('string error');
96
+ const client = new OllamaClient();
97
+ const result = await client.generate('Summarize this code');
98
+ expect(result.isErr()).toBe(true);
99
+ if (result.isErr()) {
100
+ expect(result.error).toBeInstanceOf(OllamaError);
101
+ expect(result.error.message).toContain('Unknown error');
102
+ }
103
+ });
104
+ });
105
+ describe('isAvailable', () => {
106
+ it('should return true on 200 response', async () => {
107
+ const mockResponse = { ok: true, status: 200 };
108
+ vi.mocked(globalThis.fetch).mockResolvedValue(mockResponse);
109
+ const client = new OllamaClient();
110
+ const available = await client.isAvailable();
111
+ expect(available).toBe(true);
112
+ expect(globalThis.fetch).toHaveBeenCalledWith('http://localhost:11434/api/tags', expect.objectContaining({ signal: expect.any(AbortSignal) }));
113
+ });
114
+ it('should return false on network error', async () => {
115
+ vi.mocked(globalThis.fetch).mockRejectedValue(new Error('ECONNREFUSED'));
116
+ const client = new OllamaClient();
117
+ const available = await client.isAvailable();
118
+ expect(available).toBe(false);
119
+ });
120
+ it('should return false on non-200 response', async () => {
121
+ const mockResponse = { ok: false, status: 503 };
122
+ vi.mocked(globalThis.fetch).mockResolvedValue(mockResponse);
123
+ const client = new OllamaClient();
124
+ const available = await client.isAvailable();
125
+ expect(available).toBe(false);
126
+ });
127
+ });
128
+ });
129
+ //# sourceMappingURL=ollama-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama-client.test.js","sourceRoot":"","sources":["../../src/enrichment/ollama-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Result } from 'neverthrow';
2
+ export type FileChangeStatus = 'added' | 'modified' | 'deleted' | 'renamed';
3
+ export interface FileChange {
4
+ filePath: string;
5
+ status: FileChangeStatus;
6
+ oldPath?: string;
7
+ }
8
+ export interface FileMetadata {
9
+ filePath: string;
10
+ lastModified: Date;
11
+ author: string;
12
+ commitHash: string;
13
+ }
14
+ export declare class GitError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ export interface GitClient {
18
+ getChangedFiles(since?: string): Promise<Result<FileChange[], GitError>>;
19
+ getFileMetadata(filePath: string): Promise<Result<FileMetadata, GitError>>;
20
+ getCurrentCommit(): Promise<Result<string, GitError>>;
21
+ isGitRepo(dir: string): Promise<Result<boolean, GitError>>;
22
+ }
@@ -0,0 +1,6 @@
1
+ export class GitError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'GitError';
5
+ }
6
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-client.js","sourceRoot":"","sources":["../../src/git/git-client.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,200 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { simpleGit } from 'simple-git';
6
+ import { SimpleGitClient } from './simple-git-client.js';
7
+ import { GitError } from './git-client.js';
8
+ describe('SimpleGitClient', () => {
9
+ let tempDir;
10
+ let client;
11
+ beforeEach(async () => {
12
+ tempDir = mkdtempSync(join(tmpdir(), 'coderag-git-test-'));
13
+ const git = simpleGit(tempDir);
14
+ await git.init();
15
+ await git.addConfig('user.email', 'test@coderag.dev');
16
+ await git.addConfig('user.name', 'Test Author');
17
+ // Disable GPG/SSH signing — avoids 1Password timeouts in CI/test
18
+ await git.addConfig('commit.gpgSign', 'false');
19
+ writeFileSync(join(tempDir, 'initial.txt'), 'initial content');
20
+ await git.add('initial.txt');
21
+ await git.commit('Initial commit');
22
+ client = new SimpleGitClient(tempDir);
23
+ });
24
+ afterEach(() => {
25
+ rmSync(tempDir, { recursive: true, force: true });
26
+ });
27
+ describe('isGitRepo', () => {
28
+ it('should return true for a git repository', async () => {
29
+ const result = await client.isGitRepo(tempDir);
30
+ expect(result.isOk()).toBe(true);
31
+ if (result.isOk()) {
32
+ expect(result.value).toBe(true);
33
+ }
34
+ });
35
+ it('should return false for a non-git directory', async () => {
36
+ const nonGitDir = mkdtempSync(join(tmpdir(), 'coderag-nogit-'));
37
+ try {
38
+ const result = await client.isGitRepo(nonGitDir);
39
+ expect(result.isOk()).toBe(true);
40
+ if (result.isOk()) {
41
+ expect(result.value).toBe(false);
42
+ }
43
+ }
44
+ finally {
45
+ rmSync(nonGitDir, { recursive: true, force: true });
46
+ }
47
+ });
48
+ });
49
+ describe('getCurrentCommit', () => {
50
+ it('should return the current commit hash', async () => {
51
+ const result = await client.getCurrentCommit();
52
+ expect(result.isOk()).toBe(true);
53
+ if (result.isOk()) {
54
+ expect(result.value).toMatch(/^[0-9a-f]{40}$/);
55
+ }
56
+ });
57
+ it('should return an error for a directory without commits', async () => {
58
+ const emptyDir = mkdtempSync(join(tmpdir(), 'coderag-empty-'));
59
+ try {
60
+ const git = simpleGit(emptyDir);
61
+ await git.init();
62
+ const emptyClient = new SimpleGitClient(emptyDir);
63
+ const result = await emptyClient.getCurrentCommit();
64
+ expect(result.isErr()).toBe(true);
65
+ if (result.isErr()) {
66
+ expect(result.error).toBeInstanceOf(GitError);
67
+ }
68
+ }
69
+ finally {
70
+ rmSync(emptyDir, { recursive: true, force: true });
71
+ }
72
+ });
73
+ });
74
+ describe('getChangedFiles', () => {
75
+ it('should list all tracked files when since is undefined', async () => {
76
+ const result = await client.getChangedFiles();
77
+ expect(result.isOk()).toBe(true);
78
+ if (result.isOk()) {
79
+ const filePaths = result.value.map((f) => f.filePath);
80
+ expect(filePaths).toContain('initial.txt');
81
+ }
82
+ });
83
+ it('should detect added files since a commit', async () => {
84
+ const git = simpleGit(tempDir);
85
+ const beforeCommit = (await git.revparse(['HEAD'])).trim();
86
+ writeFileSync(join(tempDir, 'newfile.ts'), 'export const x = 1;');
87
+ await git.add('newfile.ts');
88
+ await git.commit('Add new file');
89
+ const result = await client.getChangedFiles(beforeCommit);
90
+ expect(result.isOk()).toBe(true);
91
+ if (result.isOk()) {
92
+ expect(result.value).toHaveLength(1);
93
+ expect(result.value[0]?.filePath).toBe('newfile.ts');
94
+ expect(result.value[0]?.status).toBe('added');
95
+ }
96
+ });
97
+ it('should detect modified files since a commit', async () => {
98
+ const git = simpleGit(tempDir);
99
+ const beforeCommit = (await git.revparse(['HEAD'])).trim();
100
+ writeFileSync(join(tempDir, 'initial.txt'), 'modified content');
101
+ await git.add('initial.txt');
102
+ await git.commit('Modify file');
103
+ const result = await client.getChangedFiles(beforeCommit);
104
+ expect(result.isOk()).toBe(true);
105
+ if (result.isOk()) {
106
+ expect(result.value).toHaveLength(1);
107
+ expect(result.value[0]?.filePath).toBe('initial.txt');
108
+ expect(result.value[0]?.status).toBe('modified');
109
+ }
110
+ });
111
+ it('should detect deleted files since a commit', async () => {
112
+ const git = simpleGit(tempDir);
113
+ const beforeCommit = (await git.revparse(['HEAD'])).trim();
114
+ await git.rm('initial.txt');
115
+ await git.commit('Delete file');
116
+ const result = await client.getChangedFiles(beforeCommit);
117
+ expect(result.isOk()).toBe(true);
118
+ if (result.isOk()) {
119
+ expect(result.value).toHaveLength(1);
120
+ expect(result.value[0]?.filePath).toBe('initial.txt');
121
+ expect(result.value[0]?.status).toBe('deleted');
122
+ }
123
+ });
124
+ it('should return empty array when no changes since commit', async () => {
125
+ const git = simpleGit(tempDir);
126
+ const currentCommit = (await git.revparse(['HEAD'])).trim();
127
+ const result = await client.getChangedFiles(currentCommit);
128
+ expect(result.isOk()).toBe(true);
129
+ if (result.isOk()) {
130
+ expect(result.value).toHaveLength(0);
131
+ }
132
+ });
133
+ it('should return error for invalid since reference', async () => {
134
+ const result = await client.getChangedFiles('invalid-ref-abc123');
135
+ expect(result.isErr()).toBe(true);
136
+ if (result.isErr()) {
137
+ expect(result.error).toBeInstanceOf(GitError);
138
+ expect(result.error.message).toContain('Failed to get changed files');
139
+ }
140
+ });
141
+ });
142
+ describe('getFileMetadata', () => {
143
+ it('should return metadata for a committed file', async () => {
144
+ const result = await client.getFileMetadata('initial.txt');
145
+ expect(result.isOk()).toBe(true);
146
+ if (result.isOk()) {
147
+ expect(result.value.filePath).toBe('initial.txt');
148
+ expect(result.value.author).toBe('Test Author');
149
+ expect(result.value.commitHash).toMatch(/^[0-9a-f]{40}$/);
150
+ expect(result.value.lastModified).toBeInstanceOf(Date);
151
+ expect(result.value.lastModified.getTime()).not.toBeNaN();
152
+ }
153
+ });
154
+ it('should return error for a file with no git history', async () => {
155
+ const result = await client.getFileMetadata('nonexistent-file.ts');
156
+ expect(result.isErr()).toBe(true);
157
+ if (result.isErr()) {
158
+ expect(result.error).toBeInstanceOf(GitError);
159
+ expect(result.error.message).toContain('No git history found');
160
+ }
161
+ });
162
+ it('should return metadata from the latest commit for a file', async () => {
163
+ const git = simpleGit(tempDir);
164
+ writeFileSync(join(tempDir, 'initial.txt'), 'updated content');
165
+ await git.add('initial.txt');
166
+ await git.commit('Update initial file');
167
+ const currentHash = (await git.revparse(['HEAD'])).trim();
168
+ const result = await client.getFileMetadata('initial.txt');
169
+ expect(result.isOk()).toBe(true);
170
+ if (result.isOk()) {
171
+ expect(result.value.commitHash).toBe(currentHash);
172
+ }
173
+ });
174
+ it('should reject path traversal attempts', async () => {
175
+ const result = await client.getFileMetadata('../../etc/passwd');
176
+ expect(result.isErr()).toBe(true);
177
+ if (result.isErr()) {
178
+ expect(result.error).toBeInstanceOf(GitError);
179
+ expect(result.error.message).toContain('Path escapes working directory');
180
+ }
181
+ });
182
+ });
183
+ describe('getChangedFiles security', () => {
184
+ it('should reject invalid git references', async () => {
185
+ const result = await client.getChangedFiles('--option-injection');
186
+ expect(result.isErr()).toBe(true);
187
+ if (result.isErr()) {
188
+ expect(result.error).toBeInstanceOf(GitError);
189
+ expect(result.error.message).toContain('Invalid git reference');
190
+ }
191
+ });
192
+ it('should accept valid hex commit hashes', async () => {
193
+ const git = simpleGit(tempDir);
194
+ const hash = (await git.revparse(['HEAD'])).trim();
195
+ const result = await client.getChangedFiles(hash);
196
+ expect(result.isOk()).toBe(true);
197
+ });
198
+ });
199
+ });
200
+ //# sourceMappingURL=git-client.test.js.map