viberag 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 (151) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +219 -0
  3. package/dist/cli/__tests__/mcp-setup.test.d.ts +6 -0
  4. package/dist/cli/__tests__/mcp-setup.test.js +597 -0
  5. package/dist/cli/app.d.ts +2 -0
  6. package/dist/cli/app.js +238 -0
  7. package/dist/cli/commands/handlers.d.ts +57 -0
  8. package/dist/cli/commands/handlers.js +231 -0
  9. package/dist/cli/commands/index.d.ts +2 -0
  10. package/dist/cli/commands/index.js +2 -0
  11. package/dist/cli/commands/mcp-setup.d.ts +107 -0
  12. package/dist/cli/commands/mcp-setup.js +509 -0
  13. package/dist/cli/commands/useRagCommands.d.ts +23 -0
  14. package/dist/cli/commands/useRagCommands.js +180 -0
  15. package/dist/cli/components/CleanWizard.d.ts +17 -0
  16. package/dist/cli/components/CleanWizard.js +169 -0
  17. package/dist/cli/components/InitWizard.d.ts +20 -0
  18. package/dist/cli/components/InitWizard.js +370 -0
  19. package/dist/cli/components/McpSetupWizard.d.ts +37 -0
  20. package/dist/cli/components/McpSetupWizard.js +387 -0
  21. package/dist/cli/components/SearchResultsDisplay.d.ts +13 -0
  22. package/dist/cli/components/SearchResultsDisplay.js +130 -0
  23. package/dist/cli/components/WelcomeBanner.d.ts +10 -0
  24. package/dist/cli/components/WelcomeBanner.js +26 -0
  25. package/dist/cli/components/index.d.ts +1 -0
  26. package/dist/cli/components/index.js +1 -0
  27. package/dist/cli/data/mcp-editors.d.ts +80 -0
  28. package/dist/cli/data/mcp-editors.js +270 -0
  29. package/dist/cli/index.d.ts +2 -0
  30. package/dist/cli/index.js +26 -0
  31. package/dist/cli-bundle.cjs +5269 -0
  32. package/dist/common/commands/terminalSetup.d.ts +2 -0
  33. package/dist/common/commands/terminalSetup.js +144 -0
  34. package/dist/common/components/CommandSuggestions.d.ts +9 -0
  35. package/dist/common/components/CommandSuggestions.js +20 -0
  36. package/dist/common/components/StaticWithResize.d.ts +23 -0
  37. package/dist/common/components/StaticWithResize.js +62 -0
  38. package/dist/common/components/StatusBar.d.ts +8 -0
  39. package/dist/common/components/StatusBar.js +64 -0
  40. package/dist/common/components/TextInput.d.ts +12 -0
  41. package/dist/common/components/TextInput.js +239 -0
  42. package/dist/common/components/index.d.ts +3 -0
  43. package/dist/common/components/index.js +3 -0
  44. package/dist/common/hooks/index.d.ts +4 -0
  45. package/dist/common/hooks/index.js +4 -0
  46. package/dist/common/hooks/useCommandHistory.d.ts +7 -0
  47. package/dist/common/hooks/useCommandHistory.js +51 -0
  48. package/dist/common/hooks/useCtrlC.d.ts +9 -0
  49. package/dist/common/hooks/useCtrlC.js +40 -0
  50. package/dist/common/hooks/useKittyKeyboard.d.ts +10 -0
  51. package/dist/common/hooks/useKittyKeyboard.js +26 -0
  52. package/dist/common/hooks/useStaticOutputBuffer.d.ts +31 -0
  53. package/dist/common/hooks/useStaticOutputBuffer.js +58 -0
  54. package/dist/common/hooks/useTerminalResize.d.ts +28 -0
  55. package/dist/common/hooks/useTerminalResize.js +51 -0
  56. package/dist/common/hooks/useTextBuffer.d.ts +13 -0
  57. package/dist/common/hooks/useTextBuffer.js +165 -0
  58. package/dist/common/index.d.ts +13 -0
  59. package/dist/common/index.js +17 -0
  60. package/dist/common/types.d.ts +162 -0
  61. package/dist/common/types.js +1 -0
  62. package/dist/mcp/index.d.ts +12 -0
  63. package/dist/mcp/index.js +66 -0
  64. package/dist/mcp/server.d.ts +25 -0
  65. package/dist/mcp/server.js +837 -0
  66. package/dist/mcp/watcher.d.ts +86 -0
  67. package/dist/mcp/watcher.js +334 -0
  68. package/dist/rag/__tests__/grammar-smoke.test.d.ts +9 -0
  69. package/dist/rag/__tests__/grammar-smoke.test.js +161 -0
  70. package/dist/rag/__tests__/helpers.d.ts +30 -0
  71. package/dist/rag/__tests__/helpers.js +67 -0
  72. package/dist/rag/__tests__/merkle.test.d.ts +5 -0
  73. package/dist/rag/__tests__/merkle.test.js +161 -0
  74. package/dist/rag/__tests__/metadata-extraction.test.d.ts +10 -0
  75. package/dist/rag/__tests__/metadata-extraction.test.js +202 -0
  76. package/dist/rag/__tests__/multi-language.test.d.ts +13 -0
  77. package/dist/rag/__tests__/multi-language.test.js +535 -0
  78. package/dist/rag/__tests__/rag.test.d.ts +10 -0
  79. package/dist/rag/__tests__/rag.test.js +311 -0
  80. package/dist/rag/__tests__/search-exhaustive.test.d.ts +9 -0
  81. package/dist/rag/__tests__/search-exhaustive.test.js +87 -0
  82. package/dist/rag/__tests__/search-filters.test.d.ts +10 -0
  83. package/dist/rag/__tests__/search-filters.test.js +250 -0
  84. package/dist/rag/__tests__/search-modes.test.d.ts +8 -0
  85. package/dist/rag/__tests__/search-modes.test.js +133 -0
  86. package/dist/rag/config/index.d.ts +61 -0
  87. package/dist/rag/config/index.js +111 -0
  88. package/dist/rag/constants.d.ts +41 -0
  89. package/dist/rag/constants.js +57 -0
  90. package/dist/rag/embeddings/fastembed.d.ts +62 -0
  91. package/dist/rag/embeddings/fastembed.js +124 -0
  92. package/dist/rag/embeddings/gemini.d.ts +26 -0
  93. package/dist/rag/embeddings/gemini.js +116 -0
  94. package/dist/rag/embeddings/index.d.ts +10 -0
  95. package/dist/rag/embeddings/index.js +9 -0
  96. package/dist/rag/embeddings/local-4b.d.ts +28 -0
  97. package/dist/rag/embeddings/local-4b.js +51 -0
  98. package/dist/rag/embeddings/local.d.ts +29 -0
  99. package/dist/rag/embeddings/local.js +119 -0
  100. package/dist/rag/embeddings/mistral.d.ts +22 -0
  101. package/dist/rag/embeddings/mistral.js +85 -0
  102. package/dist/rag/embeddings/openai.d.ts +22 -0
  103. package/dist/rag/embeddings/openai.js +85 -0
  104. package/dist/rag/embeddings/types.d.ts +37 -0
  105. package/dist/rag/embeddings/types.js +1 -0
  106. package/dist/rag/gitignore/index.d.ts +57 -0
  107. package/dist/rag/gitignore/index.js +178 -0
  108. package/dist/rag/index.d.ts +15 -0
  109. package/dist/rag/index.js +25 -0
  110. package/dist/rag/indexer/chunker.d.ts +129 -0
  111. package/dist/rag/indexer/chunker.js +1352 -0
  112. package/dist/rag/indexer/index.d.ts +6 -0
  113. package/dist/rag/indexer/index.js +6 -0
  114. package/dist/rag/indexer/indexer.d.ts +73 -0
  115. package/dist/rag/indexer/indexer.js +356 -0
  116. package/dist/rag/indexer/types.d.ts +68 -0
  117. package/dist/rag/indexer/types.js +47 -0
  118. package/dist/rag/logger/index.d.ts +20 -0
  119. package/dist/rag/logger/index.js +75 -0
  120. package/dist/rag/manifest/index.d.ts +50 -0
  121. package/dist/rag/manifest/index.js +97 -0
  122. package/dist/rag/merkle/diff.d.ts +26 -0
  123. package/dist/rag/merkle/diff.js +95 -0
  124. package/dist/rag/merkle/hash.d.ts +34 -0
  125. package/dist/rag/merkle/hash.js +165 -0
  126. package/dist/rag/merkle/index.d.ts +68 -0
  127. package/dist/rag/merkle/index.js +298 -0
  128. package/dist/rag/merkle/node.d.ts +51 -0
  129. package/dist/rag/merkle/node.js +69 -0
  130. package/dist/rag/search/filters.d.ts +21 -0
  131. package/dist/rag/search/filters.js +100 -0
  132. package/dist/rag/search/fts.d.ts +32 -0
  133. package/dist/rag/search/fts.js +61 -0
  134. package/dist/rag/search/hybrid.d.ts +17 -0
  135. package/dist/rag/search/hybrid.js +58 -0
  136. package/dist/rag/search/index.d.ts +89 -0
  137. package/dist/rag/search/index.js +367 -0
  138. package/dist/rag/search/types.d.ts +130 -0
  139. package/dist/rag/search/types.js +4 -0
  140. package/dist/rag/search/vector.d.ts +25 -0
  141. package/dist/rag/search/vector.js +44 -0
  142. package/dist/rag/storage/index.d.ts +92 -0
  143. package/dist/rag/storage/index.js +287 -0
  144. package/dist/rag/storage/lancedb-native.d.ts +7 -0
  145. package/dist/rag/storage/lancedb-native.js +10 -0
  146. package/dist/rag/storage/schema.d.ts +23 -0
  147. package/dist/rag/storage/schema.js +50 -0
  148. package/dist/rag/storage/types.d.ts +100 -0
  149. package/dist/rag/storage/types.js +68 -0
  150. package/package.json +67 -0
  151. package/scripts/check-node-version.js +37 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * File Watcher for Auto-Indexing
3
+ *
4
+ * Watches the project directory for file changes and triggers
5
+ * incremental indexing with debouncing and batching.
6
+ */
7
+ import { type IndexStats } from '../rag/index.js';
8
+ /**
9
+ * Watcher status for reporting.
10
+ */
11
+ export interface WatcherStatus {
12
+ /** Whether the watcher is currently active */
13
+ watching: boolean;
14
+ /** Number of files being watched */
15
+ filesWatched: number;
16
+ /** Number of changes pending in the batch */
17
+ pendingChanges: number;
18
+ /** Paths of pending changes */
19
+ pendingPaths: string[];
20
+ /** Last index update timestamp (ISO string) */
21
+ lastIndexUpdate: string | null;
22
+ /** Whether the index is up to date */
23
+ indexUpToDate: boolean;
24
+ /** Last error message if any */
25
+ lastError: string | null;
26
+ }
27
+ /**
28
+ * Result of an index update triggered by the watcher.
29
+ */
30
+ export interface WatcherIndexResult {
31
+ success: boolean;
32
+ stats?: IndexStats;
33
+ error?: string;
34
+ filesProcessed: string[];
35
+ }
36
+ /**
37
+ * File watcher that triggers incremental indexing on changes.
38
+ */
39
+ export declare class FileWatcher {
40
+ private readonly projectRoot;
41
+ private config;
42
+ private watcher;
43
+ private logger;
44
+ private gitignore;
45
+ private pendingChanges;
46
+ private batchTimeout;
47
+ private debounceTimeout;
48
+ private filesWatched;
49
+ private lastIndexUpdate;
50
+ private indexUpToDate;
51
+ private lastError;
52
+ private isIndexing;
53
+ constructor(projectRoot: string);
54
+ /**
55
+ * Start watching the project directory.
56
+ */
57
+ start(): Promise<void>;
58
+ /**
59
+ * Stop the file watcher.
60
+ */
61
+ stop(): Promise<void>;
62
+ /**
63
+ * Get current watcher status.
64
+ */
65
+ getStatus(): WatcherStatus;
66
+ /**
67
+ * Check if watcher is active.
68
+ */
69
+ isWatching(): boolean;
70
+ /**
71
+ * Handle a file change event.
72
+ */
73
+ private handleChange;
74
+ /**
75
+ * Process the batch of pending changes.
76
+ */
77
+ private processBatch;
78
+ /**
79
+ * Force an immediate index update.
80
+ */
81
+ forceUpdate(): Promise<WatcherIndexResult>;
82
+ /**
83
+ * Log a message.
84
+ */
85
+ private log;
86
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * File Watcher for Auto-Indexing
3
+ *
4
+ * Watches the project directory for file changes and triggers
5
+ * incremental indexing with debouncing and batching.
6
+ */
7
+ import { watch } from 'chokidar';
8
+ import { Indexer, loadConfig, hasValidExtension, loadGitignore, createLogger, getLogsDir, } from '../rag/index.js';
9
+ /**
10
+ * File watcher that triggers incremental indexing on changes.
11
+ */
12
+ export class FileWatcher {
13
+ constructor(projectRoot) {
14
+ Object.defineProperty(this, "projectRoot", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: void 0
19
+ });
20
+ Object.defineProperty(this, "config", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: null
25
+ });
26
+ Object.defineProperty(this, "watcher", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: null
31
+ });
32
+ Object.defineProperty(this, "logger", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: null
37
+ });
38
+ Object.defineProperty(this, "gitignore", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: null
43
+ });
44
+ // Batching state
45
+ Object.defineProperty(this, "pendingChanges", {
46
+ enumerable: true,
47
+ configurable: true,
48
+ writable: true,
49
+ value: new Set()
50
+ });
51
+ Object.defineProperty(this, "batchTimeout", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: null
56
+ });
57
+ Object.defineProperty(this, "debounceTimeout", {
58
+ enumerable: true,
59
+ configurable: true,
60
+ writable: true,
61
+ value: null
62
+ });
63
+ // Status tracking
64
+ Object.defineProperty(this, "filesWatched", {
65
+ enumerable: true,
66
+ configurable: true,
67
+ writable: true,
68
+ value: 0
69
+ });
70
+ Object.defineProperty(this, "lastIndexUpdate", {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value: null
75
+ });
76
+ Object.defineProperty(this, "indexUpToDate", {
77
+ enumerable: true,
78
+ configurable: true,
79
+ writable: true,
80
+ value: true
81
+ });
82
+ Object.defineProperty(this, "lastError", {
83
+ enumerable: true,
84
+ configurable: true,
85
+ writable: true,
86
+ value: null
87
+ });
88
+ Object.defineProperty(this, "isIndexing", {
89
+ enumerable: true,
90
+ configurable: true,
91
+ writable: true,
92
+ value: false
93
+ });
94
+ this.projectRoot = projectRoot;
95
+ }
96
+ /**
97
+ * Start watching the project directory.
98
+ */
99
+ async start() {
100
+ if (this.watcher) {
101
+ return; // Already watching
102
+ }
103
+ // Load config
104
+ this.config = await loadConfig(this.projectRoot);
105
+ const watchConfig = this.config.watch;
106
+ if (!watchConfig.enabled) {
107
+ this.log('info', 'File watching disabled in config');
108
+ return;
109
+ }
110
+ // Load gitignore rules
111
+ this.gitignore = await loadGitignore(this.projectRoot);
112
+ // Initialize logger
113
+ try {
114
+ const logsDir = getLogsDir(this.projectRoot);
115
+ this.logger = createLogger(logsDir);
116
+ }
117
+ catch {
118
+ // Logs dir may not exist yet, that's ok
119
+ }
120
+ this.log('info', `Starting file watcher for ${this.projectRoot}`);
121
+ // Chokidar v5: watch directory '.' instead of glob '**/*'
122
+ // Paths are relative to cwd when cwd is set
123
+ // ignored must be a function (glob arrays removed in v4+)
124
+ const ignored = (filePath) => {
125
+ // Normalize path separators for cross-platform
126
+ const normalized = filePath.replace(/\\/g, '/');
127
+ // Check if path starts with or contains ignored directories
128
+ // Paths are relative to cwd, e.g.: '.git', 'src/index.ts', 'node_modules/pkg/...'
129
+ const ignoredDirs = ['.git', '.viberag', 'node_modules'];
130
+ for (const dir of ignoredDirs) {
131
+ if (normalized === dir ||
132
+ normalized.startsWith(`${dir}/`) ||
133
+ normalized.includes(`/${dir}/`) ||
134
+ normalized.includes(`/${dir}`)) {
135
+ return true;
136
+ }
137
+ }
138
+ return false;
139
+ };
140
+ // Create watcher - watch '.' (current directory) recursively
141
+ this.watcher = watch('.', {
142
+ cwd: this.projectRoot,
143
+ ignored,
144
+ persistent: true,
145
+ // Emit events for existing files so we can count them
146
+ ignoreInitial: false,
147
+ awaitWriteFinish: watchConfig.awaitWriteFinish
148
+ ? {
149
+ stabilityThreshold: 300,
150
+ pollInterval: 100,
151
+ }
152
+ : false,
153
+ depth: 20, // Reasonable depth limit
154
+ });
155
+ // Wait for initial scan to complete before returning
156
+ await new Promise((resolve, reject) => {
157
+ if (!this.watcher) {
158
+ reject(new Error('Watcher not initialized'));
159
+ return;
160
+ }
161
+ // Track file count
162
+ this.watcher.on('add', path => {
163
+ this.filesWatched++;
164
+ this.handleChange('add', path);
165
+ });
166
+ this.watcher.on('change', path => {
167
+ this.handleChange('change', path);
168
+ });
169
+ this.watcher.on('unlink', path => {
170
+ this.filesWatched = Math.max(0, this.filesWatched - 1);
171
+ this.handleChange('unlink', path);
172
+ });
173
+ this.watcher.on('error', error => {
174
+ const message = error instanceof Error ? error.message : String(error);
175
+ this.lastError = message;
176
+ this.log('error', `Watcher error: ${message}`);
177
+ // Don't reject on error - watcher can continue with partial coverage
178
+ });
179
+ this.watcher.on('ready', () => {
180
+ this.log('info', `Watcher ready, watching ${this.filesWatched} files`);
181
+ resolve();
182
+ });
183
+ });
184
+ }
185
+ /**
186
+ * Stop the file watcher.
187
+ */
188
+ async stop() {
189
+ if (!this.watcher) {
190
+ return;
191
+ }
192
+ this.log('info', 'Stopping file watcher');
193
+ // Clear any pending timeouts
194
+ if (this.debounceTimeout) {
195
+ clearTimeout(this.debounceTimeout);
196
+ this.debounceTimeout = null;
197
+ }
198
+ if (this.batchTimeout) {
199
+ clearTimeout(this.batchTimeout);
200
+ this.batchTimeout = null;
201
+ }
202
+ // Flush pending changes before stopping
203
+ if (this.pendingChanges.size > 0 && !this.isIndexing) {
204
+ this.log('info', 'Flushing pending changes before stop');
205
+ await this.processBatch();
206
+ }
207
+ await this.watcher.close();
208
+ this.watcher = null;
209
+ this.filesWatched = 0;
210
+ this.pendingChanges.clear();
211
+ }
212
+ /**
213
+ * Get current watcher status.
214
+ */
215
+ getStatus() {
216
+ return {
217
+ watching: this.watcher !== null,
218
+ filesWatched: this.filesWatched,
219
+ pendingChanges: this.pendingChanges.size,
220
+ pendingPaths: Array.from(this.pendingChanges).slice(0, 10), // Limit to 10
221
+ lastIndexUpdate: this.lastIndexUpdate,
222
+ indexUpToDate: this.indexUpToDate && this.pendingChanges.size === 0,
223
+ lastError: this.lastError,
224
+ };
225
+ }
226
+ /**
227
+ * Check if watcher is active.
228
+ */
229
+ isWatching() {
230
+ return this.watcher !== null;
231
+ }
232
+ /**
233
+ * Handle a file change event.
234
+ */
235
+ handleChange(event, path) {
236
+ if (!this.config || !this.gitignore)
237
+ return;
238
+ // Skip if path is ignored by gitignore
239
+ if (this.gitignore.ignores(path)) {
240
+ return;
241
+ }
242
+ // If extensions configured, filter by extension
243
+ // Empty extensions array means watch all files
244
+ if (event !== 'unlink' &&
245
+ this.config.extensions.length > 0 &&
246
+ !hasValidExtension(path, this.config.extensions)) {
247
+ return;
248
+ }
249
+ this.log('debug', `File ${event}: ${path}`);
250
+ // Add to pending changes
251
+ this.pendingChanges.add(path);
252
+ this.indexUpToDate = false;
253
+ // Debounce: reset the debounce timer on each change
254
+ if (this.debounceTimeout) {
255
+ clearTimeout(this.debounceTimeout);
256
+ }
257
+ const watchConfig = this.config.watch;
258
+ this.debounceTimeout = setTimeout(() => {
259
+ this.debounceTimeout = null;
260
+ // Start batch window if not already started
261
+ if (!this.batchTimeout) {
262
+ this.batchTimeout = setTimeout(() => {
263
+ this.batchTimeout = null;
264
+ this.processBatch();
265
+ }, watchConfig.batchWindowMs);
266
+ }
267
+ }, watchConfig.debounceMs);
268
+ }
269
+ /**
270
+ * Process the batch of pending changes.
271
+ */
272
+ async processBatch() {
273
+ if (this.pendingChanges.size === 0) {
274
+ return { success: true, filesProcessed: [] };
275
+ }
276
+ if (this.isIndexing) {
277
+ this.log('debug', 'Index already in progress, skipping batch');
278
+ return { success: false, error: 'Index in progress', filesProcessed: [] };
279
+ }
280
+ const filesToProcess = Array.from(this.pendingChanges);
281
+ this.pendingChanges.clear();
282
+ this.log('info', `Processing batch of ${filesToProcess.length} changed files`);
283
+ this.isIndexing = true;
284
+ try {
285
+ const indexer = new Indexer(this.projectRoot, this.logger ?? undefined);
286
+ const stats = await indexer.index({ force: false });
287
+ indexer.close();
288
+ this.lastIndexUpdate = new Date().toISOString();
289
+ this.indexUpToDate = true;
290
+ this.lastError = null;
291
+ this.log('info', `Index updated: ${stats.chunksAdded} added, ${stats.chunksDeleted} deleted`);
292
+ return { success: true, stats, filesProcessed: filesToProcess };
293
+ }
294
+ catch (error) {
295
+ const message = error instanceof Error ? error.message : String(error);
296
+ this.lastError = message;
297
+ this.log('error', `Index update failed: ${message}`);
298
+ // Put files back in pending queue for retry
299
+ filesToProcess.forEach(f => this.pendingChanges.add(f));
300
+ return { success: false, error: message, filesProcessed: [] };
301
+ }
302
+ finally {
303
+ this.isIndexing = false;
304
+ }
305
+ }
306
+ /**
307
+ * Force an immediate index update.
308
+ */
309
+ async forceUpdate() {
310
+ // Clear timeouts
311
+ if (this.debounceTimeout) {
312
+ clearTimeout(this.debounceTimeout);
313
+ this.debounceTimeout = null;
314
+ }
315
+ if (this.batchTimeout) {
316
+ clearTimeout(this.batchTimeout);
317
+ this.batchTimeout = null;
318
+ }
319
+ return this.processBatch();
320
+ }
321
+ /**
322
+ * Log a message.
323
+ */
324
+ log(level, message) {
325
+ const prefix = '[Watcher]';
326
+ if (this.logger) {
327
+ this.logger[level](prefix, message);
328
+ }
329
+ // Also log to stderr for MCP visibility
330
+ if (level === 'error') {
331
+ console.error(`${prefix} ${message}`);
332
+ }
333
+ }
334
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Grammar Smoke Tests
3
+ *
4
+ * Fast tests that verify each web-tree-sitter WASM grammar loads and can parse basic code.
5
+ * These tests run in ~2 seconds and catch WASM loading issues early.
6
+ *
7
+ * Critical for CI/CD to verify WASM grammars work on all platforms.
8
+ */
9
+ export {};
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Grammar Smoke Tests
3
+ *
4
+ * Fast tests that verify each web-tree-sitter WASM grammar loads and can parse basic code.
5
+ * These tests run in ~2 seconds and catch WASM loading issues early.
6
+ *
7
+ * Critical for CI/CD to verify WASM grammars work on all platforms.
8
+ */
9
+ import { describe, it, expect, beforeAll } from 'vitest';
10
+ import { createRequire } from 'node:module';
11
+ import path from 'node:path';
12
+ import Parser from 'web-tree-sitter';
13
+ const require = createRequire(import.meta.url);
14
+ const GRAMMARS = [
15
+ {
16
+ name: 'javascript',
17
+ wasmFile: 'tree-sitter-javascript.wasm',
18
+ code: 'function foo() { return 42; }',
19
+ expectedRootType: 'program',
20
+ },
21
+ {
22
+ name: 'typescript',
23
+ wasmFile: 'tree-sitter-typescript.wasm',
24
+ code: 'const x: number = 1;',
25
+ expectedRootType: 'program',
26
+ },
27
+ {
28
+ name: 'tsx',
29
+ wasmFile: 'tree-sitter-tsx.wasm',
30
+ code: 'const X = () => <div>Hello</div>;',
31
+ expectedRootType: 'program',
32
+ },
33
+ {
34
+ name: 'python',
35
+ wasmFile: 'tree-sitter-python.wasm',
36
+ code: 'def foo():\n return 42',
37
+ expectedRootType: 'module',
38
+ },
39
+ {
40
+ name: 'go',
41
+ wasmFile: 'tree-sitter-go.wasm',
42
+ code: 'package main\n\nfunc main() {}',
43
+ expectedRootType: 'source_file',
44
+ },
45
+ {
46
+ name: 'rust',
47
+ wasmFile: 'tree-sitter-rust.wasm',
48
+ code: 'fn main() { println!("Hello"); }',
49
+ expectedRootType: 'source_file',
50
+ },
51
+ {
52
+ name: 'java',
53
+ wasmFile: 'tree-sitter-java.wasm',
54
+ code: 'public class Main { public static void main(String[] args) {} }',
55
+ expectedRootType: 'program',
56
+ },
57
+ {
58
+ name: 'csharp',
59
+ wasmFile: 'tree-sitter-c_sharp.wasm',
60
+ code: 'namespace Sample { public class Foo {} }',
61
+ expectedRootType: 'compilation_unit',
62
+ },
63
+ {
64
+ name: 'kotlin',
65
+ wasmFile: 'tree-sitter-kotlin.wasm',
66
+ code: 'fun main() { println("Hello") }',
67
+ expectedRootType: 'source_file',
68
+ },
69
+ {
70
+ name: 'swift',
71
+ wasmFile: 'tree-sitter-swift.wasm',
72
+ code: 'func greet() -> String { return "Hello" }',
73
+ expectedRootType: 'source_file',
74
+ },
75
+ // Note: Dart grammar temporarily disabled due to tree-sitter version mismatch
76
+ // tree-sitter-wasms Dart WASM is version 15, but web-tree-sitter 0.24.7 supports 13-14
77
+ // TODO: Re-enable when web-tree-sitter updates or use alternative Dart WASM
78
+ // {
79
+ // name: 'dart',
80
+ // wasmFile: 'tree-sitter-dart.wasm',
81
+ // code: 'void main() { print("Hello"); }',
82
+ // expectedRootType: 'program',
83
+ // },
84
+ {
85
+ name: 'php',
86
+ wasmFile: 'tree-sitter-php.wasm',
87
+ code: '<?php\nfunction foo() { return 42; }',
88
+ expectedRootType: 'program',
89
+ },
90
+ ];
91
+ // Resolve WASM base path
92
+ const wasmPackagePath = require.resolve('tree-sitter-wasms/package.json');
93
+ const wasmBasePath = path.join(path.dirname(wasmPackagePath), 'out');
94
+ describe('Grammar Smoke Tests', () => {
95
+ let parser;
96
+ beforeAll(async () => {
97
+ // Initialize web-tree-sitter WASM module
98
+ await Parser.init();
99
+ parser = new Parser();
100
+ });
101
+ it.each(GRAMMARS)('$name: loads and parses correctly', async ({ wasmFile, code }) => {
102
+ // Load grammar from WASM
103
+ const wasmPath = path.join(wasmBasePath, wasmFile);
104
+ const language = await Parser.Language.load(wasmPath);
105
+ // Set language and parse
106
+ parser.setLanguage(language);
107
+ const tree = parser.parse(code);
108
+ // Verify parsing succeeded
109
+ expect(tree).toBeDefined();
110
+ expect(tree.rootNode).toBeDefined();
111
+ expect(tree.rootNode.type).toBeDefined();
112
+ // Verify no parse errors (syntax errors in the test code)
113
+ expect(tree.rootNode.hasError).toBe(false);
114
+ });
115
+ it.each(GRAMMARS)('$name: root node type is $expectedRootType', async ({ wasmFile, code, expectedRootType }) => {
116
+ if (!expectedRootType)
117
+ return; // Skip if no expected type
118
+ const wasmPath = path.join(wasmBasePath, wasmFile);
119
+ const language = await Parser.Language.load(wasmPath);
120
+ parser.setLanguage(language);
121
+ const tree = parser.parse(code);
122
+ expect(tree.rootNode.type).toBe(expectedRootType);
123
+ });
124
+ it('reports platform and Node.js info', () => {
125
+ const info = {
126
+ platform: process.platform,
127
+ arch: process.arch,
128
+ nodeVersion: process.version,
129
+ grammarsLoaded: GRAMMARS.length,
130
+ wasmBasePath,
131
+ };
132
+ console.log('Platform info:', JSON.stringify(info, null, 2));
133
+ // Verify we're on a supported platform
134
+ expect(['darwin', 'linux', 'win32']).toContain(process.platform);
135
+ expect(['x64', 'arm64']).toContain(process.arch);
136
+ });
137
+ it('web-tree-sitter core loads correctly', async () => {
138
+ expect(Parser).toBeDefined();
139
+ expect(typeof Parser).toBe('function');
140
+ // Parser.init() already called in beforeAll
141
+ const testParser = new Parser();
142
+ expect(testParser).toBeDefined();
143
+ });
144
+ it('all 11 grammars are tested (Dart temporarily disabled)', () => {
145
+ // Note: Dart disabled due to tree-sitter version mismatch (version 15 vs supported 13-14)
146
+ expect(GRAMMARS.length).toBe(11);
147
+ const names = GRAMMARS.map(g => g.name);
148
+ expect(names).toContain('javascript');
149
+ expect(names).toContain('typescript');
150
+ expect(names).toContain('tsx');
151
+ expect(names).toContain('python');
152
+ expect(names).toContain('go');
153
+ expect(names).toContain('rust');
154
+ expect(names).toContain('java');
155
+ expect(names).toContain('csharp');
156
+ expect(names).toContain('kotlin');
157
+ expect(names).toContain('swift');
158
+ // expect(names).toContain('dart'); // Temporarily disabled
159
+ expect(names).toContain('php');
160
+ });
161
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Test helpers for E2E tests.
3
+ */
4
+ /** Path to the checked-in test fixtures */
5
+ export declare const FIXTURES_ROOT: string;
6
+ /** Test context with temp directory and cleanup */
7
+ export interface TestContext {
8
+ projectRoot: string;
9
+ cleanup: () => Promise<void>;
10
+ }
11
+ /**
12
+ * Copy fixture directory to a unique temp directory.
13
+ */
14
+ export declare function copyFixtureToTemp(fixtureName?: string): Promise<TestContext>;
15
+ /**
16
+ * Add a new file to the temp project.
17
+ */
18
+ export declare function addFile(projectRoot: string, relativePath: string, content: string): Promise<void>;
19
+ /**
20
+ * Modify an existing file in the temp project.
21
+ */
22
+ export declare function modifyFile(projectRoot: string, relativePath: string, content: string): Promise<void>;
23
+ /**
24
+ * Delete a file from the temp project.
25
+ */
26
+ export declare function deleteFile(projectRoot: string, relativePath: string): Promise<void>;
27
+ /**
28
+ * Wait for filesystem changes to propagate (mtime resolution).
29
+ */
30
+ export declare function waitForFs(ms?: number): Promise<void>;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Test helpers for E2E tests.
3
+ */
4
+ import fs from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ /** Path to the checked-in test fixtures */
8
+ export const FIXTURES_ROOT = path.join(process.cwd(), 'test-fixtures');
9
+ /**
10
+ * Copy fixture directory to a unique temp directory.
11
+ */
12
+ export async function copyFixtureToTemp(fixtureName = 'codebase') {
13
+ const fixtureSource = path.join(FIXTURES_ROOT, fixtureName);
14
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'viberag-test-'));
15
+ await copyDirectory(fixtureSource, tempDir);
16
+ return {
17
+ projectRoot: tempDir,
18
+ cleanup: async () => {
19
+ await fs.rm(tempDir, { recursive: true, force: true });
20
+ },
21
+ };
22
+ }
23
+ /**
24
+ * Copy a directory recursively.
25
+ */
26
+ async function copyDirectory(src, dest) {
27
+ await fs.mkdir(dest, { recursive: true });
28
+ const entries = await fs.readdir(src, { withFileTypes: true });
29
+ for (const entry of entries) {
30
+ const srcPath = path.join(src, entry.name);
31
+ const destPath = path.join(dest, entry.name);
32
+ if (entry.isDirectory()) {
33
+ await copyDirectory(srcPath, destPath);
34
+ }
35
+ else {
36
+ await fs.copyFile(srcPath, destPath);
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Add a new file to the temp project.
42
+ */
43
+ export async function addFile(projectRoot, relativePath, content) {
44
+ const fullPath = path.join(projectRoot, relativePath);
45
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
46
+ await fs.writeFile(fullPath, content);
47
+ }
48
+ /**
49
+ * Modify an existing file in the temp project.
50
+ */
51
+ export async function modifyFile(projectRoot, relativePath, content) {
52
+ const fullPath = path.join(projectRoot, relativePath);
53
+ await fs.writeFile(fullPath, content);
54
+ }
55
+ /**
56
+ * Delete a file from the temp project.
57
+ */
58
+ export async function deleteFile(projectRoot, relativePath) {
59
+ const fullPath = path.join(projectRoot, relativePath);
60
+ await fs.unlink(fullPath);
61
+ }
62
+ /**
63
+ * Wait for filesystem changes to propagate (mtime resolution).
64
+ */
65
+ export async function waitForFs(ms = 100) {
66
+ return new Promise(resolve => setTimeout(resolve, ms));
67
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Merkle tree unit tests.
3
+ * Tests tree structure building and change detection (diffing).
4
+ */
5
+ export {};