@code-rag/core 0.1.0 → 0.1.1

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 (205) hide show
  1. package/package.json +4 -4
  2. package/dist/auth/audit-log.js.map +0 -1
  3. package/dist/auth/audit-log.test.d.ts +0 -1
  4. package/dist/auth/audit-log.test.js +0 -261
  5. package/dist/auth/audit-log.test.js.map +0 -1
  6. package/dist/auth/index.js.map +0 -1
  7. package/dist/auth/oidc-provider.js.map +0 -1
  8. package/dist/auth/oidc-provider.test.d.ts +0 -1
  9. package/dist/auth/oidc-provider.test.js +0 -520
  10. package/dist/auth/oidc-provider.test.js.map +0 -1
  11. package/dist/auth/rbac.js.map +0 -1
  12. package/dist/auth/rbac.test.d.ts +0 -1
  13. package/dist/auth/rbac.test.js +0 -224
  14. package/dist/auth/rbac.test.js.map +0 -1
  15. package/dist/auth/saml-provider.js.map +0 -1
  16. package/dist/auth/saml-provider.test.d.ts +0 -1
  17. package/dist/auth/saml-provider.test.js +0 -422
  18. package/dist/auth/saml-provider.test.js.map +0 -1
  19. package/dist/auth/types.js.map +0 -1
  20. package/dist/auth/types.test.d.ts +0 -1
  21. package/dist/auth/types.test.js +0 -147
  22. package/dist/auth/types.test.js.map +0 -1
  23. package/dist/backlog/ab-reference-scanner.js.map +0 -1
  24. package/dist/backlog/ab-reference-scanner.test.d.ts +0 -1
  25. package/dist/backlog/ab-reference-scanner.test.js +0 -83
  26. package/dist/backlog/ab-reference-scanner.test.js.map +0 -1
  27. package/dist/backlog/azure-devops-provider.js.map +0 -1
  28. package/dist/backlog/backlog-provider.js.map +0 -1
  29. package/dist/backlog/backlog-provider.test.d.ts +0 -1
  30. package/dist/backlog/backlog-provider.test.js +0 -426
  31. package/dist/backlog/backlog-provider.test.js.map +0 -1
  32. package/dist/backlog/clickup-provider.js.map +0 -1
  33. package/dist/backlog/clickup-provider.test.d.ts +0 -1
  34. package/dist/backlog/clickup-provider.test.js +0 -426
  35. package/dist/backlog/clickup-provider.test.js.map +0 -1
  36. package/dist/backlog/clickup-reference-scanner.js.map +0 -1
  37. package/dist/backlog/clickup-reference-scanner.test.d.ts +0 -1
  38. package/dist/backlog/clickup-reference-scanner.test.js +0 -92
  39. package/dist/backlog/clickup-reference-scanner.test.js.map +0 -1
  40. package/dist/backlog/code-linker.js.map +0 -1
  41. package/dist/backlog/code-linker.test.d.ts +0 -1
  42. package/dist/backlog/code-linker.test.js +0 -325
  43. package/dist/backlog/code-linker.test.js.map +0 -1
  44. package/dist/backlog/index.js.map +0 -1
  45. package/dist/backlog/jira-provider.js.map +0 -1
  46. package/dist/backlog/jira-provider.test.d.ts +0 -1
  47. package/dist/backlog/jira-provider.test.js +0 -449
  48. package/dist/backlog/jira-provider.test.js.map +0 -1
  49. package/dist/backlog/jira-reference-scanner.js.map +0 -1
  50. package/dist/backlog/jira-reference-scanner.test.d.ts +0 -1
  51. package/dist/backlog/jira-reference-scanner.test.js +0 -127
  52. package/dist/backlog/jira-reference-scanner.test.js.map +0 -1
  53. package/dist/backlog/types.js.map +0 -1
  54. package/dist/chunker/ast-chunker.js.map +0 -1
  55. package/dist/chunker/ast-chunker.test.d.ts +0 -1
  56. package/dist/chunker/ast-chunker.test.js +0 -391
  57. package/dist/chunker/ast-chunker.test.js.map +0 -1
  58. package/dist/chunker/chunker.js.map +0 -1
  59. package/dist/chunker/index.js.map +0 -1
  60. package/dist/config/config-parser.js.map +0 -1
  61. package/dist/config/config-parser.test.d.ts +0 -1
  62. package/dist/config/config-parser.test.js +0 -699
  63. package/dist/config/config-parser.test.js.map +0 -1
  64. package/dist/docs/confluence-provider.js.map +0 -1
  65. package/dist/docs/confluence-provider.test.d.ts +0 -1
  66. package/dist/docs/confluence-provider.test.js +0 -765
  67. package/dist/docs/confluence-provider.test.js.map +0 -1
  68. package/dist/docs/index.js.map +0 -1
  69. package/dist/docs/sharepoint-provider.js.map +0 -1
  70. package/dist/docs/sharepoint-provider.test.d.ts +0 -1
  71. package/dist/docs/sharepoint-provider.test.js +0 -873
  72. package/dist/docs/sharepoint-provider.test.js.map +0 -1
  73. package/dist/embedding/bm25-index.js.map +0 -1
  74. package/dist/embedding/bm25-index.test.d.ts +0 -1
  75. package/dist/embedding/bm25-index.test.js +0 -289
  76. package/dist/embedding/bm25-index.test.js.map +0 -1
  77. package/dist/embedding/hybrid-search.js.map +0 -1
  78. package/dist/embedding/hybrid-search.test.d.ts +0 -1
  79. package/dist/embedding/hybrid-search.test.js +0 -266
  80. package/dist/embedding/hybrid-search.test.js.map +0 -1
  81. package/dist/embedding/index.js.map +0 -1
  82. package/dist/embedding/lancedb-store.js.map +0 -1
  83. package/dist/embedding/lancedb-store.test.d.ts +0 -1
  84. package/dist/embedding/lancedb-store.test.js +0 -268
  85. package/dist/embedding/lancedb-store.test.js.map +0 -1
  86. package/dist/embedding/model-lifecycle-manager.js.map +0 -1
  87. package/dist/embedding/model-lifecycle-manager.test.d.ts +0 -1
  88. package/dist/embedding/model-lifecycle-manager.test.js +0 -642
  89. package/dist/embedding/model-lifecycle-manager.test.js.map +0 -1
  90. package/dist/embedding/ollama-embedding-provider.js.map +0 -1
  91. package/dist/embedding/ollama-embedding-provider.test.d.ts +0 -1
  92. package/dist/embedding/ollama-embedding-provider.test.js +0 -198
  93. package/dist/embedding/ollama-embedding-provider.test.js.map +0 -1
  94. package/dist/embedding/openai-compatible-embedding-provider.js.map +0 -1
  95. package/dist/embedding/openai-compatible-embedding-provider.test.d.ts +0 -1
  96. package/dist/embedding/openai-compatible-embedding-provider.test.js +0 -456
  97. package/dist/embedding/openai-compatible-embedding-provider.test.js.map +0 -1
  98. package/dist/embedding/qdrant-store.js.map +0 -1
  99. package/dist/embedding/qdrant-store.test.d.ts +0 -1
  100. package/dist/embedding/qdrant-store.test.js +0 -359
  101. package/dist/embedding/qdrant-store.test.js.map +0 -1
  102. package/dist/enrichment/index.js.map +0 -1
  103. package/dist/enrichment/nl-enricher.js.map +0 -1
  104. package/dist/enrichment/nl-enricher.test.d.ts +0 -1
  105. package/dist/enrichment/nl-enricher.test.js +0 -154
  106. package/dist/enrichment/nl-enricher.test.js.map +0 -1
  107. package/dist/enrichment/ollama-client.js.map +0 -1
  108. package/dist/enrichment/ollama-client.test.d.ts +0 -1
  109. package/dist/enrichment/ollama-client.test.js +0 -129
  110. package/dist/enrichment/ollama-client.test.js.map +0 -1
  111. package/dist/git/git-client.js.map +0 -1
  112. package/dist/git/git-client.test.d.ts +0 -1
  113. package/dist/git/git-client.test.js +0 -200
  114. package/dist/git/git-client.test.js.map +0 -1
  115. package/dist/git/ignore-filter.js.map +0 -1
  116. package/dist/git/ignore-filter.test.d.ts +0 -1
  117. package/dist/git/ignore-filter.test.js +0 -87
  118. package/dist/git/ignore-filter.test.js.map +0 -1
  119. package/dist/git/index.js.map +0 -1
  120. package/dist/git/simple-git-client.js.map +0 -1
  121. package/dist/graph/cross-repo-resolver.js.map +0 -1
  122. package/dist/graph/cross-repo-resolver.test.d.ts +0 -1
  123. package/dist/graph/cross-repo-resolver.test.js +0 -548
  124. package/dist/graph/cross-repo-resolver.test.js.map +0 -1
  125. package/dist/graph/dependency-graph.js.map +0 -1
  126. package/dist/graph/dependency-graph.test.d.ts +0 -1
  127. package/dist/graph/dependency-graph.test.js +0 -276
  128. package/dist/graph/dependency-graph.test.js.map +0 -1
  129. package/dist/graph/graph-builder.js.map +0 -1
  130. package/dist/graph/graph-builder.test.d.ts +0 -1
  131. package/dist/graph/graph-builder.test.js +0 -178
  132. package/dist/graph/graph-builder.test.js.map +0 -1
  133. package/dist/graph/import-resolver.js.map +0 -1
  134. package/dist/graph/import-resolver.test.d.ts +0 -1
  135. package/dist/graph/import-resolver.test.js +0 -282
  136. package/dist/graph/import-resolver.test.js.map +0 -1
  137. package/dist/graph/index.js.map +0 -1
  138. package/dist/index.js.map +0 -1
  139. package/dist/indexer/file-scanner.js.map +0 -1
  140. package/dist/indexer/file-scanner.test.d.ts +0 -1
  141. package/dist/indexer/file-scanner.test.js +0 -110
  142. package/dist/indexer/file-scanner.test.js.map +0 -1
  143. package/dist/indexer/incremental-indexer.js.map +0 -1
  144. package/dist/indexer/incremental-indexer.test.d.ts +0 -1
  145. package/dist/indexer/incremental-indexer.test.js +0 -266
  146. package/dist/indexer/incremental-indexer.test.js.map +0 -1
  147. package/dist/indexer/index-check.js.map +0 -1
  148. package/dist/indexer/index-check.test.d.ts +0 -1
  149. package/dist/indexer/index-check.test.js +0 -100
  150. package/dist/indexer/index-check.test.js.map +0 -1
  151. package/dist/indexer/index-state.js.map +0 -1
  152. package/dist/indexer/index-state.test.d.ts +0 -1
  153. package/dist/indexer/index-state.test.js +0 -140
  154. package/dist/indexer/index-state.test.js.map +0 -1
  155. package/dist/indexer/index.js.map +0 -1
  156. package/dist/indexer/multi-repo-indexer.js.map +0 -1
  157. package/dist/indexer/multi-repo-indexer.test.d.ts +0 -1
  158. package/dist/indexer/multi-repo-indexer.test.js +0 -238
  159. package/dist/indexer/multi-repo-indexer.test.js.map +0 -1
  160. package/dist/parser/index.js.map +0 -1
  161. package/dist/parser/language-registry.js.map +0 -1
  162. package/dist/parser/language-registry.test.d.ts +0 -1
  163. package/dist/parser/language-registry.test.js +0 -225
  164. package/dist/parser/language-registry.test.js.map +0 -1
  165. package/dist/parser/markdown-parser.js.map +0 -1
  166. package/dist/parser/markdown-parser.test.d.ts +0 -1
  167. package/dist/parser/markdown-parser.test.js +0 -600
  168. package/dist/parser/markdown-parser.test.js.map +0 -1
  169. package/dist/parser/tree-sitter-parser.js.map +0 -1
  170. package/dist/retrieval/context-expander.js.map +0 -1
  171. package/dist/retrieval/context-expander.test.d.ts +0 -1
  172. package/dist/retrieval/context-expander.test.js +0 -339
  173. package/dist/retrieval/context-expander.test.js.map +0 -1
  174. package/dist/retrieval/cross-encoder-reranker.js.map +0 -1
  175. package/dist/retrieval/cross-encoder-reranker.test.d.ts +0 -1
  176. package/dist/retrieval/cross-encoder-reranker.test.js +0 -305
  177. package/dist/retrieval/cross-encoder-reranker.test.js.map +0 -1
  178. package/dist/retrieval/index.js.map +0 -1
  179. package/dist/retrieval/query-analyzer.js.map +0 -1
  180. package/dist/retrieval/query-analyzer.test.d.ts +0 -1
  181. package/dist/retrieval/query-analyzer.test.js +0 -236
  182. package/dist/retrieval/query-analyzer.test.js.map +0 -1
  183. package/dist/retrieval/token-budget.js.map +0 -1
  184. package/dist/retrieval/token-budget.test.d.ts +0 -1
  185. package/dist/retrieval/token-budget.test.js +0 -404
  186. package/dist/retrieval/token-budget.test.js.map +0 -1
  187. package/dist/storage/azure-blob-provider.js.map +0 -1
  188. package/dist/storage/azure-blob-provider.test.d.ts +0 -1
  189. package/dist/storage/azure-blob-provider.test.js +0 -250
  190. package/dist/storage/azure-blob-provider.test.js.map +0 -1
  191. package/dist/storage/gcs-provider.js.map +0 -1
  192. package/dist/storage/gcs-provider.test.d.ts +0 -1
  193. package/dist/storage/gcs-provider.test.js +0 -299
  194. package/dist/storage/gcs-provider.test.js.map +0 -1
  195. package/dist/storage/index.js.map +0 -1
  196. package/dist/storage/s3-provider.js.map +0 -1
  197. package/dist/storage/s3-provider.test.d.ts +0 -1
  198. package/dist/storage/s3-provider.test.js +0 -329
  199. package/dist/storage/s3-provider.test.js.map +0 -1
  200. package/dist/storage/types.js.map +0 -1
  201. package/dist/types/chunk.js.map +0 -1
  202. package/dist/types/config.js.map +0 -1
  203. package/dist/types/index.js.map +0 -1
  204. package/dist/types/provider.js.map +0 -1
  205. package/dist/types/search.js.map +0 -1
@@ -1,699 +0,0 @@
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 { loadConfig, ConfigError, interpolateEnvVars } from './config-parser.js';
6
- describe('loadConfig', () => {
7
- let tempDir;
8
- beforeEach(() => {
9
- tempDir = mkdtempSync(join(tmpdir(), 'coderag-test-'));
10
- });
11
- afterEach(() => {
12
- rmSync(tempDir, { recursive: true, force: true });
13
- });
14
- it('should load a valid config file', async () => {
15
- const configContent = `
16
- version: "1"
17
- project:
18
- name: test-project
19
- languages: auto
20
- ingestion:
21
- maxTokensPerChunk: 256
22
- exclude:
23
- - node_modules
24
- - dist
25
- embedding:
26
- provider: ollama
27
- model: nomic-embed-text
28
- dimensions: 768
29
- llm:
30
- provider: ollama
31
- model: "qwen2.5-coder:7b"
32
- search:
33
- topK: 5
34
- vectorWeight: 0.6
35
- bm25Weight: 0.4
36
- storage:
37
- path: .coderag
38
- `;
39
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
40
- const result = await loadConfig(tempDir);
41
- expect(result.isOk()).toBe(true);
42
- if (result.isOk()) {
43
- expect(result.value.project.name).toBe('test-project');
44
- expect(result.value.ingestion.maxTokensPerChunk).toBe(256);
45
- expect(result.value.search.topK).toBe(5);
46
- expect(result.value.search.vectorWeight).toBe(0.6);
47
- expect(result.value.search.bm25Weight).toBe(0.4);
48
- }
49
- });
50
- it('should accept languages as an array of strings', async () => {
51
- const configContent = `
52
- version: "1"
53
- project:
54
- name: multi-lang
55
- languages:
56
- - typescript
57
- - python
58
- `;
59
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
60
- const result = await loadConfig(tempDir);
61
- expect(result.isOk()).toBe(true);
62
- if (result.isOk()) {
63
- expect(result.value.project.languages).toEqual(['typescript', 'python']);
64
- }
65
- });
66
- it('should apply defaults for missing fields', async () => {
67
- const configContent = `
68
- version: "1"
69
- project:
70
- name: minimal-project
71
- `;
72
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
73
- const result = await loadConfig(tempDir);
74
- expect(result.isOk()).toBe(true);
75
- if (result.isOk()) {
76
- expect(result.value.project.name).toBe('minimal-project');
77
- expect(result.value.project.languages).toBe('auto');
78
- expect(result.value.ingestion.maxTokensPerChunk).toBe(512);
79
- expect(result.value.embedding.provider).toBe('auto');
80
- expect(result.value.embedding.model).toBe('nomic-embed-text');
81
- expect(result.value.embedding.dimensions).toBe(768);
82
- expect(result.value.embedding.autoStart).toBe(true);
83
- expect(result.value.embedding.autoStop).toBe(false);
84
- expect(result.value.embedding.docker).toEqual({ image: 'ollama/ollama', gpu: 'auto' });
85
- expect(result.value.llm.provider).toBe('ollama');
86
- expect(result.value.search.topK).toBe(10);
87
- expect(result.value.storage.path).toBe('.coderag');
88
- }
89
- });
90
- it('should return error on invalid YAML', async () => {
91
- const invalidYaml = `
92
- version: "1"
93
- project:
94
- name: test
95
- invalid: [unclosed bracket
96
- `;
97
- writeFileSync(join(tempDir, '.coderag.yaml'), invalidYaml);
98
- const result = await loadConfig(tempDir);
99
- expect(result.isErr()).toBe(true);
100
- if (result.isErr()) {
101
- expect(result.error).toBeInstanceOf(ConfigError);
102
- expect(result.error.message).toContain('Invalid YAML');
103
- }
104
- });
105
- it('should return error when file does not exist', async () => {
106
- const result = await loadConfig(tempDir);
107
- expect(result.isErr()).toBe(true);
108
- if (result.isErr()) {
109
- expect(result.error).toBeInstanceOf(ConfigError);
110
- expect(result.error.message).toContain('Config file not found');
111
- }
112
- });
113
- it('should return error when config file contains a scalar value', async () => {
114
- writeFileSync(join(tempDir, '.coderag.yaml'), 'just a string');
115
- const result = await loadConfig(tempDir);
116
- expect(result.isErr()).toBe(true);
117
- if (result.isErr()) {
118
- expect(result.error).toBeInstanceOf(ConfigError);
119
- expect(result.error.message).toContain('empty or not a valid YAML object');
120
- }
121
- });
122
- it('should return error when config file is empty', async () => {
123
- writeFileSync(join(tempDir, '.coderag.yaml'), '');
124
- const result = await loadConfig(tempDir);
125
- expect(result.isErr()).toBe(true);
126
- if (result.isErr()) {
127
- expect(result.error).toBeInstanceOf(ConfigError);
128
- expect(result.error.message).toContain('empty or not a valid YAML object');
129
- }
130
- });
131
- // --- Zod validation edge case tests ---
132
- it('should return validation error for invalid dimensions (negative)', async () => {
133
- const configContent = `
134
- version: "1"
135
- project:
136
- name: bad-dims
137
- embedding:
138
- provider: ollama
139
- model: nomic-embed-text
140
- dimensions: -5
141
- `;
142
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
143
- const result = await loadConfig(tempDir);
144
- expect(result.isErr()).toBe(true);
145
- if (result.isErr()) {
146
- expect(result.error).toBeInstanceOf(ConfigError);
147
- expect(result.error.message).toContain('Config validation failed');
148
- expect(result.error.message).toContain('dimensions');
149
- }
150
- });
151
- it('should return validation error for non-integer dimensions', async () => {
152
- const configContent = `
153
- version: "1"
154
- project:
155
- name: bad-dims
156
- embedding:
157
- provider: ollama
158
- model: nomic-embed-text
159
- dimensions: 768.5
160
- `;
161
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
162
- const result = await loadConfig(tempDir);
163
- expect(result.isErr()).toBe(true);
164
- if (result.isErr()) {
165
- expect(result.error).toBeInstanceOf(ConfigError);
166
- expect(result.error.message).toContain('Config validation failed');
167
- expect(result.error.message).toContain('dimensions');
168
- }
169
- });
170
- it('should return validation error for empty embedding provider', async () => {
171
- const configContent = `
172
- version: "1"
173
- project:
174
- name: no-provider
175
- embedding:
176
- provider: ""
177
- model: nomic-embed-text
178
- dimensions: 768
179
- `;
180
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
181
- const result = await loadConfig(tempDir);
182
- expect(result.isErr()).toBe(true);
183
- if (result.isErr()) {
184
- expect(result.error).toBeInstanceOf(ConfigError);
185
- expect(result.error.message).toContain('Config validation failed');
186
- expect(result.error.message).toContain('provider');
187
- }
188
- });
189
- it('should return validation error for empty LLM provider', async () => {
190
- const configContent = `
191
- version: "1"
192
- project:
193
- name: no-llm
194
- llm:
195
- provider: ""
196
- model: "qwen2.5-coder:7b"
197
- `;
198
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
199
- const result = await loadConfig(tempDir);
200
- expect(result.isErr()).toBe(true);
201
- if (result.isErr()) {
202
- expect(result.error).toBeInstanceOf(ConfigError);
203
- expect(result.error.message).toContain('Config validation failed');
204
- expect(result.error.message).toContain('provider');
205
- }
206
- });
207
- it('should return validation error for negative topK', async () => {
208
- const configContent = `
209
- version: "1"
210
- project:
211
- name: bad-topk
212
- search:
213
- topK: -3
214
- vectorWeight: 0.7
215
- bm25Weight: 0.3
216
- `;
217
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
218
- const result = await loadConfig(tempDir);
219
- expect(result.isErr()).toBe(true);
220
- if (result.isErr()) {
221
- expect(result.error).toBeInstanceOf(ConfigError);
222
- expect(result.error.message).toContain('Config validation failed');
223
- expect(result.error.message).toContain('topK');
224
- }
225
- });
226
- it('should return validation error for vectorWeight out of range', async () => {
227
- const configContent = `
228
- version: "1"
229
- project:
230
- name: bad-weight
231
- search:
232
- topK: 10
233
- vectorWeight: 1.5
234
- bm25Weight: 0.3
235
- `;
236
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
237
- const result = await loadConfig(tempDir);
238
- expect(result.isErr()).toBe(true);
239
- if (result.isErr()) {
240
- expect(result.error).toBeInstanceOf(ConfigError);
241
- expect(result.error.message).toContain('Config validation failed');
242
- expect(result.error.message).toContain('vectorWeight');
243
- }
244
- });
245
- it('should return validation error for bm25Weight out of range', async () => {
246
- const configContent = `
247
- version: "1"
248
- project:
249
- name: bad-bm25
250
- search:
251
- topK: 10
252
- vectorWeight: 0.7
253
- bm25Weight: -0.1
254
- `;
255
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
256
- const result = await loadConfig(tempDir);
257
- expect(result.isErr()).toBe(true);
258
- if (result.isErr()) {
259
- expect(result.error).toBeInstanceOf(ConfigError);
260
- expect(result.error.message).toContain('Config validation failed');
261
- expect(result.error.message).toContain('bm25Weight');
262
- }
263
- });
264
- it('should return validation error for empty version string', async () => {
265
- const configContent = `
266
- version: ""
267
- project:
268
- name: no-version
269
- `;
270
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
271
- const result = await loadConfig(tempDir);
272
- expect(result.isErr()).toBe(true);
273
- if (result.isErr()) {
274
- expect(result.error).toBeInstanceOf(ConfigError);
275
- expect(result.error.message).toContain('Config validation failed');
276
- expect(result.error.message).toContain('version');
277
- }
278
- });
279
- it('should return validation error for empty project name', async () => {
280
- const configContent = `
281
- version: "1"
282
- project:
283
- name: ""
284
- `;
285
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
286
- const result = await loadConfig(tempDir);
287
- expect(result.isErr()).toBe(true);
288
- if (result.isErr()) {
289
- expect(result.error).toBeInstanceOf(ConfigError);
290
- expect(result.error.message).toContain('Config validation failed');
291
- expect(result.error.message).toContain('name');
292
- }
293
- });
294
- it('should return validation error for empty storage path', async () => {
295
- const configContent = `
296
- version: "1"
297
- project:
298
- name: no-path
299
- storage:
300
- path: ""
301
- `;
302
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
303
- const result = await loadConfig(tempDir);
304
- expect(result.isErr()).toBe(true);
305
- if (result.isErr()) {
306
- expect(result.error).toBeInstanceOf(ConfigError);
307
- expect(result.error.message).toContain('Config validation failed');
308
- expect(result.error.message).toContain('path');
309
- }
310
- });
311
- // --- Multi-repo config tests ---
312
- it('should load a valid multi-repo config with repos array', async () => {
313
- const configContent = `
314
- version: "1"
315
- project:
316
- name: multi-repo
317
- languages: auto
318
- repos:
319
- - path: /home/dev/repo-a
320
- name: repo-a
321
- languages:
322
- - typescript
323
- exclude:
324
- - dist
325
- - path: /home/dev/repo-b
326
- name: repo-b
327
- `;
328
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
329
- const result = await loadConfig(tempDir);
330
- expect(result.isOk()).toBe(true);
331
- if (result.isOk()) {
332
- expect(result.value.repos).toBeDefined();
333
- expect(result.value.repos).toHaveLength(2);
334
- expect(result.value.repos[0].path).toBe('/home/dev/repo-a');
335
- expect(result.value.repos[0].name).toBe('repo-a');
336
- expect(result.value.repos[0].languages).toEqual(['typescript']);
337
- expect(result.value.repos[0].exclude).toEqual(['dist']);
338
- expect(result.value.repos[1].path).toBe('/home/dev/repo-b');
339
- expect(result.value.repos[1].name).toBe('repo-b');
340
- }
341
- });
342
- it('should load config without repos field (single repo, backwards compatible)', async () => {
343
- const configContent = `
344
- version: "1"
345
- project:
346
- name: single-repo
347
- languages: auto
348
- `;
349
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
350
- const result = await loadConfig(tempDir);
351
- expect(result.isOk()).toBe(true);
352
- if (result.isOk()) {
353
- expect(result.value.repos).toBeUndefined();
354
- expect(result.value.project.name).toBe('single-repo');
355
- }
356
- });
357
- it('should return validation error for repo entry missing path', async () => {
358
- const configContent = `
359
- version: "1"
360
- project:
361
- name: bad-repo
362
- repos:
363
- - name: no-path-repo
364
- `;
365
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
366
- const result = await loadConfig(tempDir);
367
- expect(result.isErr()).toBe(true);
368
- if (result.isErr()) {
369
- expect(result.error).toBeInstanceOf(ConfigError);
370
- expect(result.error.message).toContain('Config validation failed');
371
- }
372
- });
373
- it('should accept repo entries with per-repo language and exclude overrides', async () => {
374
- const configContent = `
375
- version: "1"
376
- project:
377
- name: overrides
378
- repos:
379
- - path: /repos/frontend
380
- languages:
381
- - typescript
382
- - javascript
383
- exclude:
384
- - node_modules
385
- - .next
386
- - path: /repos/backend
387
- languages:
388
- - python
389
- exclude:
390
- - __pycache__
391
- - .venv
392
- `;
393
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
394
- const result = await loadConfig(tempDir);
395
- expect(result.isOk()).toBe(true);
396
- if (result.isOk()) {
397
- expect(result.value.repos).toHaveLength(2);
398
- expect(result.value.repos[0].languages).toEqual(['typescript', 'javascript']);
399
- expect(result.value.repos[0].exclude).toEqual(['node_modules', '.next']);
400
- expect(result.value.repos[1].languages).toEqual(['python']);
401
- expect(result.value.repos[1].exclude).toEqual(['__pycache__', '.venv']);
402
- }
403
- });
404
- it('should return validation error for repo with empty path', async () => {
405
- const configContent = `
406
- version: "1"
407
- project:
408
- name: empty-path
409
- repos:
410
- - path: ""
411
- `;
412
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
413
- const result = await loadConfig(tempDir);
414
- expect(result.isErr()).toBe(true);
415
- if (result.isErr()) {
416
- expect(result.error).toBeInstanceOf(ConfigError);
417
- expect(result.error.message).toContain('Config validation failed');
418
- expect(result.error.message).toContain('path');
419
- }
420
- });
421
- it('should accept empty repos array', async () => {
422
- const configContent = `
423
- version: "1"
424
- project:
425
- name: empty-repos
426
- repos: []
427
- `;
428
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
429
- const result = await loadConfig(tempDir);
430
- expect(result.isOk()).toBe(true);
431
- if (result.isOk()) {
432
- expect(result.value.repos).toEqual([]);
433
- }
434
- });
435
- it('should accept boundary values for weights (0 and 1)', async () => {
436
- const configContent = `
437
- version: "1"
438
- project:
439
- name: boundary-weights
440
- search:
441
- topK: 10
442
- vectorWeight: 0
443
- bm25Weight: 1
444
- `;
445
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
446
- const result = await loadConfig(tempDir);
447
- expect(result.isOk()).toBe(true);
448
- if (result.isOk()) {
449
- expect(result.value.search.vectorWeight).toBe(0);
450
- expect(result.value.search.bm25Weight).toBe(1);
451
- }
452
- });
453
- it('should return validation error for zero topK', async () => {
454
- const configContent = `
455
- version: "1"
456
- project:
457
- name: zero-topk
458
- search:
459
- topK: 0
460
- vectorWeight: 0.7
461
- bm25Weight: 0.3
462
- `;
463
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
464
- const result = await loadConfig(tempDir);
465
- expect(result.isErr()).toBe(true);
466
- if (result.isErr()) {
467
- expect(result.error).toBeInstanceOf(ConfigError);
468
- expect(result.error.message).toContain('Config validation failed');
469
- expect(result.error.message).toContain('topK');
470
- }
471
- });
472
- });
473
- describe('loadConfig — openaiCompatible embedding config', () => {
474
- let tempDir;
475
- beforeEach(() => {
476
- tempDir = mkdtempSync(join(tmpdir(), 'coderag-openai-'));
477
- });
478
- afterEach(() => {
479
- rmSync(tempDir, { recursive: true, force: true });
480
- });
481
- it('should load openaiCompatible config with camelCase keys', async () => {
482
- const configContent = `
483
- version: "1"
484
- project:
485
- name: openai-test
486
- embedding:
487
- provider: openai-compatible
488
- model: text-embedding-3-small
489
- dimensions: 1536
490
- openaiCompatible:
491
- baseUrl: https://api.openai.com/v1
492
- apiKey: sk-test-key
493
- maxBatchSize: 50
494
- `;
495
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
496
- const result = await loadConfig(tempDir);
497
- expect(result.isOk()).toBe(true);
498
- if (result.isOk()) {
499
- expect(result.value.embedding.provider).toBe('openai-compatible');
500
- expect(result.value.embedding.openaiCompatible).toBeDefined();
501
- expect(result.value.embedding.openaiCompatible.baseUrl).toBe('https://api.openai.com/v1');
502
- expect(result.value.embedding.openaiCompatible.apiKey).toBe('sk-test-key');
503
- expect(result.value.embedding.openaiCompatible.maxBatchSize).toBe(50);
504
- }
505
- });
506
- it('should load openaiCompatible config with snake_case YAML keys', async () => {
507
- const configContent = `
508
- version: "1"
509
- project:
510
- name: openai-snake
511
- embedding:
512
- provider: openai-compatible
513
- model: nomic-embed-text
514
- dimensions: 768
515
- openai_compatible:
516
- base_url: http://localhost:1234/v1
517
- api_key: lm-studio-key
518
- max_batch_size: 200
519
- `;
520
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
521
- const result = await loadConfig(tempDir);
522
- expect(result.isOk()).toBe(true);
523
- if (result.isOk()) {
524
- expect(result.value.embedding.openaiCompatible).toBeDefined();
525
- expect(result.value.embedding.openaiCompatible.baseUrl).toBe('http://localhost:1234/v1');
526
- expect(result.value.embedding.openaiCompatible.apiKey).toBe('lm-studio-key');
527
- expect(result.value.embedding.openaiCompatible.maxBatchSize).toBe(200);
528
- }
529
- });
530
- it('should apply defaults for openaiCompatible when minimal config provided', async () => {
531
- const configContent = `
532
- version: "1"
533
- project:
534
- name: openai-defaults
535
- embedding:
536
- provider: openai-compatible
537
- model: nomic-embed-text
538
- dimensions: 768
539
- openaiCompatible:
540
- baseUrl: http://localhost:11434/v1
541
- `;
542
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
543
- const result = await loadConfig(tempDir);
544
- expect(result.isOk()).toBe(true);
545
- if (result.isOk()) {
546
- expect(result.value.embedding.openaiCompatible).toBeDefined();
547
- expect(result.value.embedding.openaiCompatible.baseUrl).toBe('http://localhost:11434/v1');
548
- expect(result.value.embedding.openaiCompatible.maxBatchSize).toBe(100);
549
- }
550
- });
551
- it('should load config without openaiCompatible section (backwards compatible)', async () => {
552
- const configContent = `
553
- version: "1"
554
- project:
555
- name: no-openai
556
- embedding:
557
- provider: ollama
558
- model: nomic-embed-text
559
- dimensions: 768
560
- `;
561
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
562
- const result = await loadConfig(tempDir);
563
- expect(result.isOk()).toBe(true);
564
- if (result.isOk()) {
565
- expect(result.value.embedding.provider).toBe('ollama');
566
- expect(result.value.embedding.openaiCompatible).toBeUndefined();
567
- }
568
- });
569
- it('should return validation error for empty baseUrl in openaiCompatible', async () => {
570
- const configContent = `
571
- version: "1"
572
- project:
573
- name: bad-openai
574
- embedding:
575
- provider: openai-compatible
576
- model: nomic-embed-text
577
- dimensions: 768
578
- openaiCompatible:
579
- baseUrl: ""
580
- maxBatchSize: 100
581
- `;
582
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
583
- const result = await loadConfig(tempDir);
584
- expect(result.isErr()).toBe(true);
585
- if (result.isErr()) {
586
- expect(result.error).toBeInstanceOf(ConfigError);
587
- expect(result.error.message).toContain('Config validation failed');
588
- expect(result.error.message).toContain('baseUrl');
589
- }
590
- });
591
- });
592
- describe('interpolateEnvVars', () => {
593
- const ORIG_ENV = { ...process.env };
594
- afterEach(() => {
595
- process.env = { ...ORIG_ENV };
596
- });
597
- it('should replace ${VAR} with process.env value', () => {
598
- process.env['TEST_TOKEN'] = 'secret123';
599
- const result = interpolateEnvVars({ pat: '${TEST_TOKEN}' });
600
- expect(result).toEqual({ pat: 'secret123' });
601
- });
602
- it('should replace multiple vars in one string', () => {
603
- process.env['HOST'] = 'localhost';
604
- process.env['PORT'] = '8080';
605
- const result = interpolateEnvVars({ url: 'http://${HOST}:${PORT}' });
606
- expect(result).toEqual({ url: 'http://localhost:8080' });
607
- });
608
- it('should return ConfigError for missing env var', () => {
609
- delete process.env['MISSING_VAR'];
610
- const result = interpolateEnvVars({ key: '${MISSING_VAR}' });
611
- expect(result).toBeInstanceOf(ConfigError);
612
- expect(result.message).toContain('MISSING_VAR');
613
- });
614
- it('should NOT interpolate escaped \\${VAR}', () => {
615
- const result = interpolateEnvVars({ key: '\\${NOT_A_VAR}' });
616
- expect(result).toEqual({ key: '${NOT_A_VAR}' });
617
- });
618
- it('should traverse nested objects recursively', () => {
619
- process.env['NESTED_VAL'] = 'deep';
620
- const result = interpolateEnvVars({
621
- level1: { level2: { value: '${NESTED_VAL}' } },
622
- });
623
- expect(result).toEqual({
624
- level1: { level2: { value: 'deep' } },
625
- });
626
- });
627
- it('should traverse arrays', () => {
628
- process.env['ARR_VAL'] = 'item';
629
- const result = interpolateEnvVars({ items: ['${ARR_VAL}', 'literal'] });
630
- expect(result).toEqual({ items: ['item', 'literal'] });
631
- });
632
- it('should leave non-string values untouched', () => {
633
- const result = interpolateEnvVars({ num: 42, bool: true, nil: null });
634
- expect(result).toEqual({ num: 42, bool: true, nil: null });
635
- });
636
- });
637
- describe('loadConfig with env var interpolation', () => {
638
- let tempDir;
639
- const ORIG_ENV = { ...process.env };
640
- beforeEach(() => {
641
- tempDir = mkdtempSync(join(tmpdir(), 'coderag-env-'));
642
- });
643
- afterEach(() => {
644
- rmSync(tempDir, { recursive: true, force: true });
645
- process.env = { ...ORIG_ENV };
646
- });
647
- it('should resolve ${ADO_PAT} in backlog config', async () => {
648
- process.env['ADO_PAT'] = 'my-secret-pat';
649
- const configContent = `
650
- version: "1"
651
- project:
652
- name: env-test
653
- backlog:
654
- provider: ado
655
- config:
656
- organization: myorg
657
- project: MyProject
658
- pat: \${ADO_PAT}
659
- `;
660
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
661
- const result = await loadConfig(tempDir);
662
- expect(result.isOk()).toBe(true);
663
- if (result.isOk()) {
664
- expect(result.value.backlog?.config?.['pat']).toBe('my-secret-pat');
665
- }
666
- });
667
- it('should return error when referenced env var is missing', async () => {
668
- delete process.env['MISSING_PAT'];
669
- const configContent = `
670
- version: "1"
671
- project:
672
- name: missing-env
673
- backlog:
674
- provider: ado
675
- config:
676
- pat: \${MISSING_PAT}
677
- `;
678
- writeFileSync(join(tempDir, '.coderag.yaml'), configContent);
679
- const result = await loadConfig(tempDir);
680
- expect(result.isErr()).toBe(true);
681
- if (result.isErr()) {
682
- expect(result.error).toBeInstanceOf(ConfigError);
683
- expect(result.error.message).toContain('MISSING_PAT');
684
- }
685
- });
686
- });
687
- describe('barrel exports', () => {
688
- it('should re-export loadConfig and ConfigError from the package entry point', async () => {
689
- const barrel = await import('../index.js');
690
- expect(barrel.loadConfig).toBeDefined();
691
- expect(barrel.ConfigError).toBeDefined();
692
- expect(barrel.EmbedError).toBeDefined();
693
- expect(barrel.StoreError).toBeDefined();
694
- expect(barrel.LLMError).toBeDefined();
695
- expect(barrel.ParseError).toBeDefined();
696
- expect(barrel.ChunkError).toBeDefined();
697
- });
698
- });
699
- //# sourceMappingURL=config-parser.test.js.map