viberag 0.2.0 → 0.3.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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/mcp-setup.d.ts +1 -1
  3. package/dist/cli/commands/mcp-setup.js +23 -3
  4. package/dist/cli/components/CleanWizard.js +16 -1
  5. package/dist/cli/components/InitWizard.js +37 -15
  6. package/dist/common/types.d.ts +2 -2
  7. package/dist/mcp/index.js +5 -1
  8. package/dist/mcp/warmup.d.ts +5 -0
  9. package/dist/mcp/warmup.js +7 -0
  10. package/dist/rag/config/index.d.ts +4 -0
  11. package/dist/rag/config/index.js +37 -13
  12. package/dist/rag/embeddings/gemini.js +34 -7
  13. package/dist/rag/embeddings/index.d.ts +1 -0
  14. package/dist/rag/embeddings/index.js +1 -0
  15. package/dist/rag/embeddings/mistral.d.ts +2 -2
  16. package/dist/rag/embeddings/mistral.js +18 -5
  17. package/dist/rag/embeddings/openai.js +22 -3
  18. package/dist/rag/embeddings/validate.d.ts +22 -0
  19. package/dist/rag/embeddings/validate.js +148 -0
  20. package/dist/rag/index.d.ts +1 -1
  21. package/dist/rag/index.js +1 -1
  22. package/dist/rag/indexer/chunker.js +31 -19
  23. package/dist/rag/indexer/indexer.d.ts +10 -0
  24. package/dist/rag/indexer/indexer.js +88 -53
  25. package/dist/rag/search/index.d.ts +6 -0
  26. package/dist/rag/search/index.js +35 -9
  27. package/dist/rag/storage/index.d.ts +15 -1
  28. package/dist/rag/storage/index.js +108 -21
  29. package/package.json +33 -4
  30. package/dist/cli/__tests__/mcp-setup-comprehensive.test.d.ts +0 -10
  31. package/dist/cli/__tests__/mcp-setup-comprehensive.test.js +0 -515
  32. package/dist/cli/__tests__/mcp-setup-global.test.d.ts +0 -7
  33. package/dist/cli/__tests__/mcp-setup-global.test.js +0 -577
  34. package/dist/cli/__tests__/mcp-setup.test.d.ts +0 -6
  35. package/dist/cli/__tests__/mcp-setup.test.js +0 -704
  36. package/dist/rag/__tests__/grammar-smoke.test.d.ts +0 -9
  37. package/dist/rag/__tests__/grammar-smoke.test.js +0 -161
  38. package/dist/rag/__tests__/helpers.d.ts +0 -30
  39. package/dist/rag/__tests__/helpers.js +0 -67
  40. package/dist/rag/__tests__/merkle.test.d.ts +0 -5
  41. package/dist/rag/__tests__/merkle.test.js +0 -161
  42. package/dist/rag/__tests__/metadata-extraction.test.d.ts +0 -10
  43. package/dist/rag/__tests__/metadata-extraction.test.js +0 -202
  44. package/dist/rag/__tests__/multi-language.test.d.ts +0 -13
  45. package/dist/rag/__tests__/multi-language.test.js +0 -535
  46. package/dist/rag/__tests__/rag.test.d.ts +0 -10
  47. package/dist/rag/__tests__/rag.test.js +0 -311
  48. package/dist/rag/__tests__/search-exhaustive.test.d.ts +0 -9
  49. package/dist/rag/__tests__/search-exhaustive.test.js +0 -87
  50. package/dist/rag/__tests__/search-filters.test.d.ts +0 -10
  51. package/dist/rag/__tests__/search-filters.test.js +0 -250
  52. package/dist/rag/__tests__/search-modes.test.d.ts +0 -8
  53. package/dist/rag/__tests__/search-modes.test.js +0 -133
@@ -1,704 +0,0 @@
1
- /**
2
- * MCP Setup Integration Tests
3
- *
4
- * Tests for config generation, file creation, merging, and edge cases.
5
- */
6
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
- import fs from 'node:fs/promises';
8
- import path from 'node:path';
9
- import os from 'node:os';
10
- import { writeMcpConfig, generateMcpConfig, generateViberagConfig, generateOpenCodeViberagConfig, generateTomlConfig, readJsonConfig, mergeConfig, mergeTomlConfig, hasViberagConfig, hasViberagTomlConfig, isAlreadyConfigured, configExists, removeViberagConfig, removeViberagFromConfig, removeViberagFromTomlConfig, findConfiguredEditors, } from '../commands/mcp-setup.js';
11
- import { EDITORS, getEditor } from '../data/mcp-editors.js';
12
- /**
13
- * Create a temporary directory for test isolation.
14
- */
15
- async function createTempDir() {
16
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'viberag-mcp-test-'));
17
- return {
18
- dir,
19
- cleanup: async () => {
20
- await fs.rm(dir, { recursive: true, force: true });
21
- },
22
- };
23
- }
24
- /**
25
- * Write a test config file.
26
- */
27
- async function writeTestConfig(dir, relativePath, content) {
28
- const fullPath = path.join(dir, relativePath);
29
- const dirPath = path.dirname(fullPath);
30
- await fs.mkdir(dirPath, { recursive: true });
31
- const data = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
32
- await fs.writeFile(fullPath, data, 'utf-8');
33
- }
34
- /**
35
- * Read a test config file.
36
- */
37
- async function readTestConfig(dir, relativePath) {
38
- const fullPath = path.join(dir, relativePath);
39
- const content = await fs.readFile(fullPath, 'utf-8');
40
- return JSON.parse(content);
41
- }
42
- // =============================================================================
43
- // Config Generation Tests
44
- // =============================================================================
45
- describe('MCP Config Generation', () => {
46
- it('generateViberagConfig returns correct structure', () => {
47
- const config = generateViberagConfig();
48
- expect(config).toEqual({
49
- command: 'npx',
50
- args: ['-y', 'viberag-mcp'],
51
- });
52
- });
53
- it('generateMcpConfig uses correct key for Claude Code', () => {
54
- const editor = getEditor('claude-code');
55
- const config = generateMcpConfig(editor);
56
- expect(config).toHaveProperty('mcpServers');
57
- expect(config.mcpServers.viberag).toBeDefined();
58
- });
59
- it('generateMcpConfig uses "mcp" key for OpenCode', () => {
60
- const editor = getEditor('opencode');
61
- const config = generateMcpConfig(editor);
62
- expect(config).toHaveProperty('mcp');
63
- expect(config.mcp.viberag).toBeDefined();
64
- });
65
- it('generateMcpConfig uses "servers" key for VS Code', () => {
66
- const editor = getEditor('vscode');
67
- const config = generateMcpConfig(editor);
68
- expect(config).toHaveProperty('servers');
69
- expect(config).not.toHaveProperty('mcpServers');
70
- });
71
- it('generateMcpConfig uses "context_servers" key for Zed', () => {
72
- const editor = getEditor('zed');
73
- const config = generateMcpConfig(editor);
74
- expect(config).toHaveProperty('context_servers');
75
- expect(config).not.toHaveProperty('mcpServers');
76
- });
77
- it('generateTomlConfig returns valid TOML', () => {
78
- const toml = generateTomlConfig();
79
- expect(toml).toContain('[mcp_servers.viberag]');
80
- expect(toml).toContain('command = "npx"');
81
- expect(toml).toContain('args = ["-y", "viberag-mcp"]');
82
- });
83
- });
84
- // =============================================================================
85
- // TOML Config Tests (OpenAI Codex)
86
- // =============================================================================
87
- describe('TOML Config Functions', () => {
88
- it('hasViberagTomlConfig detects viberag section', () => {
89
- const toml = `[mcp_servers.viberag]
90
- command = "npx"
91
- args = ["-y", "viberag-mcp"]
92
- `;
93
- expect(hasViberagTomlConfig(toml)).toBe(true);
94
- });
95
- it('hasViberagTomlConfig returns false when viberag missing', () => {
96
- const toml = `[mcp_servers.other]
97
- command = "other-cmd"
98
- `;
99
- expect(hasViberagTomlConfig(toml)).toBe(false);
100
- });
101
- it('hasViberagTomlConfig handles empty content', () => {
102
- expect(hasViberagTomlConfig('')).toBe(false);
103
- });
104
- it('mergeTomlConfig appends viberag to existing config', () => {
105
- const existing = `[mcp_servers.context7]
106
- command = "npx"
107
- args = ["context7-mcp"]
108
- `;
109
- const merged = mergeTomlConfig(existing);
110
- expect(merged).toContain('[mcp_servers.context7]');
111
- expect(merged).toContain('[mcp_servers.viberag]');
112
- expect(merged).toContain('args = ["-y", "viberag-mcp"]');
113
- });
114
- it('mergeTomlConfig does not duplicate if viberag exists', () => {
115
- const existing = generateTomlConfig();
116
- const merged = mergeTomlConfig(existing);
117
- expect(merged).toBe(existing);
118
- });
119
- it('mergeTomlConfig handles empty file', () => {
120
- const merged = mergeTomlConfig('');
121
- expect(merged).toContain('[mcp_servers.viberag]');
122
- });
123
- it('removeViberagFromTomlConfig removes viberag section', () => {
124
- const toml = `[mcp_servers.context7]
125
- command = "npx"
126
- args = ["context7-mcp"]
127
-
128
- [mcp_servers.viberag]
129
- command = "npx"
130
- args = ["-y", "viberag-mcp"]
131
- `;
132
- const result = removeViberagFromTomlConfig(toml);
133
- expect(result).not.toBeNull();
134
- expect(result).toContain('[mcp_servers.context7]');
135
- expect(result).not.toContain('[mcp_servers.viberag]');
136
- });
137
- it('removeViberagFromTomlConfig returns null if viberag not present', () => {
138
- const toml = `[mcp_servers.context7]
139
- command = "npx"
140
- args = ["context7-mcp"]
141
- `;
142
- const result = removeViberagFromTomlConfig(toml);
143
- expect(result).toBeNull();
144
- });
145
- it('removeViberagFromTomlConfig preserves other sections', () => {
146
- const toml = `[general]
147
- api_key = "test"
148
-
149
- [mcp_servers.viberag]
150
- command = "npx"
151
- args = ["-y", "viberag-mcp"]
152
-
153
- [other_section]
154
- value = 123
155
- `;
156
- const result = removeViberagFromTomlConfig(toml);
157
- expect(result).not.toBeNull();
158
- expect(result).toContain('[general]');
159
- expect(result).toContain('[other_section]');
160
- expect(result).not.toContain('[mcp_servers.viberag]');
161
- });
162
- });
163
- // =============================================================================
164
- // Project-Level Config Creation Tests
165
- // =============================================================================
166
- describe('Project-Level Config Creation', () => {
167
- let ctx;
168
- beforeEach(async () => {
169
- ctx = await createTempDir();
170
- });
171
- afterEach(async () => {
172
- await ctx.cleanup();
173
- });
174
- it('creates .mcp.json for Claude Code in empty project', async () => {
175
- const editor = getEditor('claude-code');
176
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
177
- expect(result.success).toBe(true);
178
- expect(result.method).toBe('file-created');
179
- expect(result.configPath).toBe(path.join(ctx.dir, '.mcp.json'));
180
- const config = await readTestConfig(ctx.dir, '.mcp.json');
181
- expect(config.mcpServers.viberag
182
- .command).toBe('npx');
183
- });
184
- it('creates .vscode/mcp.json with parent directory', async () => {
185
- const editor = getEditor('vscode');
186
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
187
- expect(result.success).toBe(true);
188
- expect(result.method).toBe('file-created');
189
- // Verify .vscode directory was created
190
- const vscodeDir = path.join(ctx.dir, '.vscode');
191
- const stat = await fs.stat(vscodeDir);
192
- expect(stat.isDirectory()).toBe(true);
193
- const config = await readTestConfig(ctx.dir, '.vscode/mcp.json');
194
- expect(config.servers.viberag).toBeDefined();
195
- });
196
- it('creates .cursor/mcp.json for Cursor', async () => {
197
- const editor = getEditor('cursor');
198
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
199
- expect(result.success).toBe(true);
200
- expect(result.configPath).toContain('.cursor/mcp.json');
201
- const config = await readTestConfig(ctx.dir, '.cursor/mcp.json');
202
- expect(config.mcpServers.viberag).toBeDefined();
203
- });
204
- it('creates .roo/mcp.json for Roo Code', async () => {
205
- const editor = getEditor('roo-code');
206
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
207
- expect(result.success).toBe(true);
208
- expect(result.configPath).toContain('.roo/mcp.json');
209
- const config = await readTestConfig(ctx.dir, '.roo/mcp.json');
210
- expect(config.mcpServers.viberag).toBeDefined();
211
- });
212
- });
213
- // =============================================================================
214
- // Config Merging Tests
215
- // =============================================================================
216
- describe('Config Merging', () => {
217
- let ctx;
218
- beforeEach(async () => {
219
- ctx = await createTempDir();
220
- });
221
- afterEach(async () => {
222
- await ctx.cleanup();
223
- });
224
- it('merges viberag into existing mcpServers', async () => {
225
- // Setup: Create existing config with another server
226
- await writeTestConfig(ctx.dir, '.mcp.json', {
227
- mcpServers: {
228
- 'other-server': {
229
- command: 'node',
230
- args: ['other.js'],
231
- },
232
- },
233
- });
234
- const editor = getEditor('claude-code');
235
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
236
- expect(result.success).toBe(true);
237
- expect(result.method).toBe('file-merged');
238
- const config = (await readTestConfig(ctx.dir, '.mcp.json'));
239
- // Both servers should exist
240
- expect(config.mcpServers['other-server']).toBeDefined();
241
- expect(config.mcpServers.viberag).toBeDefined();
242
- });
243
- it('preserves existing servers during merge', async () => {
244
- const existingConfig = {
245
- mcpServers: {
246
- github: { command: 'npx', args: ['github-mcp'] },
247
- filesystem: { command: 'npx', args: ['fs-mcp'] },
248
- },
249
- };
250
- await writeTestConfig(ctx.dir, '.mcp.json', existingConfig);
251
- const editor = getEditor('claude-code');
252
- await writeMcpConfig(editor, 'project', ctx.dir);
253
- const config = (await readTestConfig(ctx.dir, '.mcp.json'));
254
- expect(Object.keys(config.mcpServers)).toHaveLength(3);
255
- expect(config.mcpServers['github']).toBeDefined();
256
- expect(config.mcpServers['filesystem']).toBeDefined();
257
- expect(config.mcpServers['viberag']).toBeDefined();
258
- });
259
- it('merges into VS Code config with servers key', async () => {
260
- await writeTestConfig(ctx.dir, '.vscode/mcp.json', {
261
- servers: {
262
- existing: { command: 'existing' },
263
- },
264
- });
265
- const editor = getEditor('vscode');
266
- await writeMcpConfig(editor, 'project', ctx.dir);
267
- const config = (await readTestConfig(ctx.dir, '.vscode/mcp.json'));
268
- expect(config.servers['existing']).toBeDefined();
269
- expect(config.servers['viberag']).toBeDefined();
270
- });
271
- it('mergeConfig function works correctly', () => {
272
- const existing = {
273
- mcpServers: {
274
- other: { command: 'other' },
275
- },
276
- someOtherKey: 'value',
277
- };
278
- const editor = getEditor('claude-code');
279
- const merged = mergeConfig(existing, editor);
280
- expect(merged.mcpServers['other']).toBeDefined();
281
- expect(merged.mcpServers['viberag']).toBeDefined();
282
- expect(merged['someOtherKey']).toBe('value');
283
- });
284
- it('mergeConfig handles empty servers object', () => {
285
- const existing = { mcpServers: {} };
286
- const editor = getEditor('claude-code');
287
- const merged = mergeConfig(existing, editor);
288
- expect(merged.mcpServers['viberag']).toBeDefined();
289
- });
290
- it('mergeConfig creates servers object if missing', () => {
291
- const existing = { otherKey: 'value' };
292
- const editor = getEditor('claude-code');
293
- const merged = mergeConfig(existing, editor);
294
- expect(merged.mcpServers['viberag']).toBeDefined();
295
- });
296
- it('generateMcpConfig works with OpenCode nested mcp structure', () => {
297
- const existing = {
298
- mcp: {
299
- 'other-server': { command: 'other' },
300
- },
301
- someOtherKey: 'value',
302
- };
303
- const editor = getEditor('opencode');
304
- const merged = mergeConfig(existing, editor);
305
- expect(merged.mcp['other-server']).toBeDefined();
306
- expect(merged.mcp['viberag']).toBeDefined();
307
- expect(merged['someOtherKey']).toBe('value');
308
- });
309
- });
310
- // =============================================================================
311
- // OpenCode-Specific Tests
312
- // =============================================================================
313
- describe('OpenCode MCP Configuration', () => {
314
- it('generateOpenCodeViberagConfig returns correct OpenCode format', () => {
315
- const config = generateOpenCodeViberagConfig();
316
- expect(config).toEqual({
317
- type: 'local',
318
- command: ['npx', '-y', 'viberag-mcp'],
319
- });
320
- });
321
- it('generateMcpConfig uses OpenCode format for OpenCode editor', () => {
322
- const editor = getEditor('opencode');
323
- const config = generateMcpConfig(editor);
324
- expect(config).toHaveProperty('mcp');
325
- expect(config.mcp
326
- .viberag.type).toBe('local');
327
- expect(config.mcp.viberag.command).toEqual(['npx', '-y', 'viberag-mcp']);
328
- });
329
- it('generateMcpConfig does NOT use args key for OpenCode', () => {
330
- const editor = getEditor('opencode');
331
- const config = generateMcpConfig(editor);
332
- const viberagConfig = config.mcp.viberag;
333
- expect(viberagConfig).not.toHaveProperty('args');
334
- expect(viberagConfig).toHaveProperty('command');
335
- });
336
- it('mergeConfig uses OpenCode format when merging for OpenCode', () => {
337
- const existing = {
338
- mcp: {
339
- 'other-server': { command: 'other' },
340
- },
341
- };
342
- const editor = getEditor('opencode');
343
- const merged = mergeConfig(existing, editor);
344
- expect(merged.mcp['viberag']?.type).toBe('local');
345
- expect(merged.mcp['viberag']?.command).toEqual([
346
- 'npx',
347
- '-y',
348
- 'viberag-mcp',
349
- ]);
350
- expect(merged.mcp['other-server']).toBeDefined();
351
- });
352
- it('hasViberagConfig detects viberag in OpenCode config', () => {
353
- const config = {
354
- mcp: {
355
- viberag: { type: 'local', command: ['npx', '-y', 'viberag-mcp'] },
356
- },
357
- };
358
- const editor = getEditor('opencode');
359
- expect(hasViberagConfig(config, editor)).toBe(true);
360
- });
361
- it('hasViberagConfig returns false for missing viberag in OpenCode', () => {
362
- const config = {
363
- mcp: {
364
- other: { type: 'local', command: ['npx', 'other'] },
365
- },
366
- };
367
- const editor = getEditor('opencode');
368
- expect(hasViberagConfig(config, editor)).toBe(false);
369
- });
370
- });
371
- // =============================================================================
372
- // Already Configured Detection Tests
373
- // =============================================================================
374
- describe('Already Configured Detection', () => {
375
- let ctx;
376
- beforeEach(async () => {
377
- ctx = await createTempDir();
378
- });
379
- afterEach(async () => {
380
- await ctx.cleanup();
381
- });
382
- it('hasViberagConfig returns true when viberag exists', () => {
383
- const config = {
384
- mcpServers: {
385
- viberag: { command: 'npx', args: ['viberag-mcp'] },
386
- },
387
- };
388
- const editor = getEditor('claude-code');
389
- expect(hasViberagConfig(config, editor)).toBe(true);
390
- });
391
- it('hasViberagConfig returns false when viberag missing', () => {
392
- const config = {
393
- mcpServers: {
394
- other: { command: 'other' },
395
- },
396
- };
397
- const editor = getEditor('claude-code');
398
- expect(hasViberagConfig(config, editor)).toBe(false);
399
- });
400
- it('isAlreadyConfigured detects existing viberag config', async () => {
401
- await writeTestConfig(ctx.dir, '.mcp.json', {
402
- mcpServers: {
403
- viberag: { command: 'npx', args: ['viberag-mcp'] },
404
- },
405
- });
406
- const editor = getEditor('claude-code');
407
- const result = await isAlreadyConfigured(editor, 'project', ctx.dir);
408
- expect(result).toBe(true);
409
- });
410
- it('isAlreadyConfigured returns false for empty project', async () => {
411
- const editor = getEditor('claude-code');
412
- const result = await isAlreadyConfigured(editor, 'project', ctx.dir);
413
- expect(result).toBe(false);
414
- });
415
- it('writeMcpConfig reports already configured', async () => {
416
- // Pre-configure viberag
417
- await writeTestConfig(ctx.dir, '.mcp.json', {
418
- mcpServers: {
419
- viberag: { command: 'npx', args: ['viberag-mcp'] },
420
- },
421
- });
422
- const editor = getEditor('claude-code');
423
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
424
- expect(result.success).toBe(true);
425
- expect(result.method).toBe('file-merged');
426
- expect(result.error).toBe('Already configured');
427
- });
428
- });
429
- // =============================================================================
430
- // Edge Cases Tests
431
- // =============================================================================
432
- describe('Edge Cases', () => {
433
- let ctx;
434
- beforeEach(async () => {
435
- ctx = await createTempDir();
436
- });
437
- afterEach(async () => {
438
- await ctx.cleanup();
439
- });
440
- it('handles empty existing config file', async () => {
441
- await writeTestConfig(ctx.dir, '.mcp.json', {});
442
- const editor = getEditor('claude-code');
443
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
444
- expect(result.success).toBe(true);
445
- const config = (await readTestConfig(ctx.dir, '.mcp.json'));
446
- expect(config.mcpServers['viberag']).toBeDefined();
447
- });
448
- it('handles malformed JSON gracefully', async () => {
449
- // Write invalid JSON
450
- const configPath = path.join(ctx.dir, '.mcp.json');
451
- await fs.writeFile(configPath, '{ invalid json }', 'utf-8');
452
- const editor = getEditor('claude-code');
453
- const result = await writeMcpConfig(editor, 'project', ctx.dir);
454
- expect(result.success).toBe(false);
455
- expect(result.error).toContain('parse');
456
- });
457
- it('readJsonConfig returns null for non-existent file', async () => {
458
- const result = await readJsonConfig(path.join(ctx.dir, 'nonexistent.json'));
459
- expect(result).toBeNull();
460
- });
461
- it('configExists returns false for non-existent file', async () => {
462
- const result = await configExists(path.join(ctx.dir, 'nonexistent.json'));
463
- expect(result).toBe(false);
464
- });
465
- it('configExists returns true for existing file', async () => {
466
- await writeTestConfig(ctx.dir, 'test.json', {});
467
- const result = await configExists(path.join(ctx.dir, 'test.json'));
468
- expect(result).toBe(true);
469
- });
470
- });
471
- // =============================================================================
472
- // Editor Configuration Data Tests
473
- // =============================================================================
474
- describe('Editor Configuration Data', () => {
475
- it('JSON editors with canAutoCreate true support project scope', () => {
476
- // JSON editors that can auto-create should support project scope
477
- // TOML editors (like Codex) may only support global scope
478
- const autoCreateJsonEditors = EDITORS.filter(e => e.canAutoCreate && e.configFormat === 'json');
479
- for (const editor of autoCreateJsonEditors) {
480
- expect(editor.supportsProject).toBe(true);
481
- }
482
- });
483
- it('Codex editor supports TOML auto-create for global scope only', () => {
484
- const codex = EDITORS.find(e => e.id === 'codex');
485
- expect(codex).toBeDefined();
486
- expect(codex.configFormat).toBe('toml');
487
- expect(codex.canAutoCreate).toBe(true);
488
- expect(codex.supportsGlobal).toBe(true);
489
- expect(codex.supportsProject).toBe(false);
490
- });
491
- it('all editors have valid docsUrl', () => {
492
- for (const editor of EDITORS) {
493
- expect(editor.docsUrl).toMatch(/^https?:\/\//);
494
- }
495
- });
496
- it('getEditor returns correct editor by id', () => {
497
- const claude = getEditor('claude-code');
498
- expect(claude?.name).toBe('Claude Code');
499
- const vscode = getEditor('vscode');
500
- expect(vscode?.name).toBe('VS Code Copilot');
501
- const nonexistent = getEditor('nonexistent');
502
- expect(nonexistent).toBeUndefined();
503
- });
504
- it('each editor has unique id', () => {
505
- const ids = EDITORS.map(e => e.id);
506
- const uniqueIds = new Set(ids);
507
- expect(uniqueIds.size).toBe(ids.length);
508
- });
509
- it('editors with both scopes have correct paths', () => {
510
- const dualScopeEditors = EDITORS.filter(e => e.supportsGlobal && e.supportsProject);
511
- for (const editor of dualScopeEditors) {
512
- // Project path should be set
513
- expect(editor.projectConfigPath).toBeTruthy();
514
- // Global path may be null (for UI-only global like VS Code, Roo Code)
515
- }
516
- });
517
- });
518
- // =============================================================================
519
- // Config Removal Tests
520
- // =============================================================================
521
- describe('Config Removal', () => {
522
- let ctx;
523
- beforeEach(async () => {
524
- ctx = await createTempDir();
525
- });
526
- afterEach(async () => {
527
- await ctx.cleanup();
528
- });
529
- it('removes viberag while preserving other servers', async () => {
530
- // Setup: config with viberag and another server
531
- await writeTestConfig(ctx.dir, '.mcp.json', {
532
- mcpServers: {
533
- viberag: { command: 'npx', args: ['viberag-mcp'] },
534
- github: { command: 'npx', args: ['github-mcp'] },
535
- },
536
- });
537
- const editor = getEditor('claude-code');
538
- const result = await removeViberagConfig(editor, 'project', ctx.dir);
539
- expect(result.success).toBe(true);
540
- expect(result.editor).toBe('claude-code');
541
- // File should still exist with github server preserved
542
- const config = (await readTestConfig(ctx.dir, '.mcp.json'));
543
- expect(config.mcpServers['github']).toBeDefined();
544
- expect(config.mcpServers['viberag']).toBeUndefined();
545
- });
546
- it('removes viberag and keeps file with empty servers', async () => {
547
- // Setup: config with only viberag
548
- await writeTestConfig(ctx.dir, '.mcp.json', {
549
- mcpServers: {
550
- viberag: { command: 'npx', args: ['viberag-mcp'] },
551
- },
552
- });
553
- const editor = getEditor('claude-code');
554
- const result = await removeViberagConfig(editor, 'project', ctx.dir);
555
- expect(result.success).toBe(true);
556
- // File should still exist with empty mcpServers
557
- const config = (await readTestConfig(ctx.dir, '.mcp.json'));
558
- expect(config.mcpServers).toBeDefined();
559
- expect(Object.keys(config.mcpServers)).toHaveLength(0);
560
- });
561
- it('returns error when viberag not configured', async () => {
562
- // Setup: config without viberag
563
- await writeTestConfig(ctx.dir, '.mcp.json', {
564
- mcpServers: {
565
- github: { command: 'npx', args: ['github-mcp'] },
566
- },
567
- });
568
- const editor = getEditor('claude-code');
569
- const result = await removeViberagConfig(editor, 'project', ctx.dir);
570
- expect(result.success).toBe(false);
571
- expect(result.error).toContain('not configured');
572
- });
573
- it('returns error when config file does not exist', async () => {
574
- const editor = getEditor('claude-code');
575
- const result = await removeViberagConfig(editor, 'project', ctx.dir);
576
- expect(result.success).toBe(false);
577
- expect(result.error).toContain('does not exist');
578
- });
579
- it('handles VS Code config with servers key', async () => {
580
- await writeTestConfig(ctx.dir, '.vscode/mcp.json', {
581
- servers: {
582
- viberag: { command: 'npx', args: ['viberag-mcp'] },
583
- other: { command: 'other' },
584
- },
585
- });
586
- const editor = getEditor('vscode');
587
- const result = await removeViberagConfig(editor, 'project', ctx.dir);
588
- expect(result.success).toBe(true);
589
- const config = (await readTestConfig(ctx.dir, '.vscode/mcp.json'));
590
- expect(config.servers['other']).toBeDefined();
591
- expect(config.servers['viberag']).toBeUndefined();
592
- });
593
- });
594
- // =============================================================================
595
- // removeViberagFromConfig Helper Tests
596
- // =============================================================================
597
- describe('removeViberagFromConfig helper', () => {
598
- it('returns null when servers key is missing', () => {
599
- const existing = { otherKey: 'value' };
600
- const editor = getEditor('claude-code');
601
- const result = removeViberagFromConfig(existing, editor);
602
- expect(result).toBeNull();
603
- });
604
- it('returns null when servers is not an object', () => {
605
- const existing = { mcpServers: 'not-an-object' };
606
- const editor = getEditor('claude-code');
607
- const result = removeViberagFromConfig(existing, editor);
608
- expect(result).toBeNull();
609
- });
610
- it('returns null when viberag not in servers', () => {
611
- const existing = {
612
- mcpServers: {
613
- github: { command: 'github' },
614
- },
615
- };
616
- const editor = getEditor('claude-code');
617
- const result = removeViberagFromConfig(existing, editor);
618
- expect(result).toBeNull();
619
- });
620
- it('returns modified config with viberag removed', () => {
621
- const existing = {
622
- mcpServers: {
623
- viberag: { command: 'npx' },
624
- github: { command: 'github' },
625
- },
626
- otherKey: 'preserved',
627
- };
628
- const editor = getEditor('claude-code');
629
- const result = removeViberagFromConfig(existing, editor);
630
- expect(result).not.toBeNull();
631
- expect(result.mcpServers['viberag']).toBeUndefined();
632
- expect(result.mcpServers['github']).toBeDefined();
633
- expect(result.otherKey).toBe('preserved');
634
- });
635
- it('works with VS Code servers key', () => {
636
- const existing = {
637
- servers: {
638
- viberag: { command: 'npx' },
639
- other: { command: 'other' },
640
- },
641
- };
642
- const editor = getEditor('vscode');
643
- const result = removeViberagFromConfig(existing, editor);
644
- expect(result).not.toBeNull();
645
- expect(result.servers['viberag']).toBeUndefined();
646
- expect(result.servers['other']).toBeDefined();
647
- });
648
- });
649
- // =============================================================================
650
- // findConfiguredEditors Tests
651
- // =============================================================================
652
- describe('findConfiguredEditors', () => {
653
- let ctx;
654
- beforeEach(async () => {
655
- ctx = await createTempDir();
656
- });
657
- afterEach(async () => {
658
- await ctx.cleanup();
659
- });
660
- it('finds project-scope editors with viberag configured', async () => {
661
- // Setup: Claude Code and Cursor both configured
662
- await writeTestConfig(ctx.dir, '.mcp.json', {
663
- mcpServers: { viberag: { command: 'npx' } },
664
- });
665
- await writeTestConfig(ctx.dir, '.cursor/mcp.json', {
666
- mcpServers: { viberag: { command: 'npx' } },
667
- });
668
- const result = await findConfiguredEditors(ctx.dir);
669
- expect(result.projectScope.length).toBeGreaterThanOrEqual(2);
670
- const ids = result.projectScope.map(e => e.editor.id);
671
- expect(ids).toContain('claude-code');
672
- expect(ids).toContain('cursor');
673
- });
674
- it('returns empty project scope when no configs exist', async () => {
675
- const result = await findConfiguredEditors(ctx.dir);
676
- // Project scope should be empty since we didn't create any configs
677
- expect(result.projectScope).toHaveLength(0);
678
- // Global scope might contain editors if they're configured globally on this machine
679
- // This is expected behavior - the function checks real global configs
680
- });
681
- it('does not include editors without viberag', async () => {
682
- // Setup: Claude Code has viberag, Cursor has different server
683
- await writeTestConfig(ctx.dir, '.mcp.json', {
684
- mcpServers: { viberag: { command: 'npx' } },
685
- });
686
- await writeTestConfig(ctx.dir, '.cursor/mcp.json', {
687
- mcpServers: { github: { command: 'npx' } },
688
- });
689
- const result = await findConfiguredEditors(ctx.dir);
690
- const ids = result.projectScope.map(e => e.editor.id);
691
- expect(ids).toContain('claude-code');
692
- expect(ids).not.toContain('cursor');
693
- });
694
- it('ConfiguredEditorInfo includes scope information', async () => {
695
- await writeTestConfig(ctx.dir, '.mcp.json', {
696
- mcpServers: { viberag: { command: 'npx' } },
697
- });
698
- const result = await findConfiguredEditors(ctx.dir);
699
- const claudeInfo = result.projectScope.find(e => e.editor.id === 'claude-code');
700
- expect(claudeInfo).toBeDefined();
701
- expect(claudeInfo?.scope).toBe('project');
702
- expect(claudeInfo?.editor.name).toBe('Claude Code');
703
- });
704
- });