code-auditor-mcp 1.0.0 → 1.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 (113) hide show
  1. package/README.md +72 -2
  2. package/dist/api/index.d.ts +1 -0
  3. package/dist/api/index.d.ts.map +1 -0
  4. package/dist/api/index.js +3 -0
  5. package/dist/api/index.js.map +1 -0
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +229 -22
  8. package/dist/cli.js.map +1 -1
  9. package/dist/codeIndexDb.d.ts +150 -0
  10. package/dist/codeIndexDb.d.ts.map +1 -0
  11. package/dist/codeIndexDb.js +847 -0
  12. package/dist/codeIndexDb.js.map +1 -0
  13. package/dist/codeIndexService.d.ts +66 -0
  14. package/dist/codeIndexService.d.ts.map +1 -0
  15. package/dist/codeIndexService.js +201 -0
  16. package/dist/codeIndexService.js.map +1 -0
  17. package/dist/config/configLoader.d.ts.map +1 -1
  18. package/dist/config/configLoader.js +17 -1
  19. package/dist/config/configLoader.js.map +1 -1
  20. package/dist/config/defaults.d.ts +9 -0
  21. package/dist/config/defaults.d.ts.map +1 -1
  22. package/dist/config/defaults.js +9 -0
  23. package/dist/config/defaults.js.map +1 -1
  24. package/dist/constants.d.ts +8 -0
  25. package/dist/constants.d.ts.map +1 -0
  26. package/dist/constants.js +14 -0
  27. package/dist/constants.js.map +1 -0
  28. package/dist/functionScanner.d.ts +30 -0
  29. package/dist/functionScanner.d.ts.map +1 -0
  30. package/dist/functionScanner.js +264 -0
  31. package/dist/functionScanner.js.map +1 -0
  32. package/dist/generators/AWSQConfigGenerator.d.ts +8 -0
  33. package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -0
  34. package/dist/generators/AWSQConfigGenerator.js +33 -0
  35. package/dist/generators/AWSQConfigGenerator.js.map +1 -0
  36. package/dist/generators/AiderConfigGenerator.d.ts +8 -0
  37. package/dist/generators/AiderConfigGenerator.d.ts.map +1 -0
  38. package/dist/generators/AiderConfigGenerator.js +23 -0
  39. package/dist/generators/AiderConfigGenerator.js.map +1 -0
  40. package/dist/generators/BaseConfigGenerator.d.ts +51 -0
  41. package/dist/generators/BaseConfigGenerator.d.ts.map +1 -0
  42. package/dist/generators/BaseConfigGenerator.js +67 -0
  43. package/dist/generators/BaseConfigGenerator.js.map +1 -0
  44. package/dist/generators/ClaudeConfigGenerator.d.ts +14 -0
  45. package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -0
  46. package/dist/generators/ClaudeConfigGenerator.js +70 -0
  47. package/dist/generators/ClaudeConfigGenerator.js.map +1 -0
  48. package/dist/generators/ClineConfigGenerator.d.ts +9 -0
  49. package/dist/generators/ClineConfigGenerator.d.ts.map +1 -0
  50. package/dist/generators/ClineConfigGenerator.js +26 -0
  51. package/dist/generators/ClineConfigGenerator.js.map +1 -0
  52. package/dist/generators/CodeiumConfigGenerator.d.ts +8 -0
  53. package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -0
  54. package/dist/generators/CodeiumConfigGenerator.js +30 -0
  55. package/dist/generators/CodeiumConfigGenerator.js.map +1 -0
  56. package/dist/generators/ConfigGeneratorFactory.d.ts +35 -0
  57. package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -0
  58. package/dist/generators/ConfigGeneratorFactory.js +77 -0
  59. package/dist/generators/ConfigGeneratorFactory.js.map +1 -0
  60. package/dist/generators/ContinueConfigGenerator.d.ts +12 -0
  61. package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -0
  62. package/dist/generators/ContinueConfigGenerator.js +99 -0
  63. package/dist/generators/ContinueConfigGenerator.js.map +1 -0
  64. package/dist/generators/CopilotConfigGenerator.d.ts +12 -0
  65. package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -0
  66. package/dist/generators/CopilotConfigGenerator.js +77 -0
  67. package/dist/generators/CopilotConfigGenerator.js.map +1 -0
  68. package/dist/generators/CursorConfigGenerator.d.ts +12 -0
  69. package/dist/generators/CursorConfigGenerator.d.ts.map +1 -0
  70. package/dist/generators/CursorConfigGenerator.js +80 -0
  71. package/dist/generators/CursorConfigGenerator.js.map +1 -0
  72. package/dist/generators/JetBrainsConfigGenerator.d.ts +8 -0
  73. package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -0
  74. package/dist/generators/JetBrainsConfigGenerator.js +36 -0
  75. package/dist/generators/JetBrainsConfigGenerator.js.map +1 -0
  76. package/dist/generators/VSCodeConfigGenerator.d.ts +8 -0
  77. package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -0
  78. package/dist/generators/VSCodeConfigGenerator.js +29 -0
  79. package/dist/generators/VSCodeConfigGenerator.js.map +1 -0
  80. package/dist/generators/index.d.ts +13 -0
  81. package/dist/generators/index.d.ts.map +1 -0
  82. package/dist/generators/index.js +14 -0
  83. package/dist/generators/index.js.map +1 -0
  84. package/dist/mcp-standalone.js +0 -0
  85. package/dist/mcp.js +483 -2
  86. package/dist/mcp.js.map +1 -1
  87. package/dist/search/QueryParser.d.ts +60 -0
  88. package/dist/search/QueryParser.d.ts.map +1 -0
  89. package/dist/search/QueryParser.js +330 -0
  90. package/dist/search/QueryParser.js.map +1 -0
  91. package/dist/search/index.d.ts +7 -0
  92. package/dist/search/index.d.ts.map +1 -0
  93. package/dist/search/index.js +6 -0
  94. package/dist/search/index.js.map +1 -0
  95. package/dist/test-multi-strategy-search.d.ts +3 -0
  96. package/dist/test-multi-strategy-search.d.ts.map +1 -0
  97. package/dist/test-multi-strategy-search.js +170 -0
  98. package/dist/test-multi-strategy-search.js.map +1 -0
  99. package/dist/types.d.ts +136 -0
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/ui/InteractivePrompts.d.ts +37 -0
  102. package/dist/ui/InteractivePrompts.d.ts.map +1 -0
  103. package/dist/ui/InteractivePrompts.js +147 -0
  104. package/dist/ui/InteractivePrompts.js.map +1 -0
  105. package/dist/ui/index.d.ts +2 -0
  106. package/dist/ui/index.d.ts.map +1 -0
  107. package/dist/ui/index.js +3 -0
  108. package/dist/ui/index.js.map +1 -0
  109. package/dist/utils/jsDocParser.d.ts +84 -0
  110. package/dist/utils/jsDocParser.d.ts.map +1 -0
  111. package/dist/utils/jsDocParser.js +366 -0
  112. package/dist/utils/jsDocParser.js.map +1 -0
  113. package/package.json +31 -11
@@ -0,0 +1,847 @@
1
+ /**
2
+ * Code Index Database using LokiJS + FlexSearch
3
+ * Pure JavaScript implementation with no native dependencies
4
+ */
5
+ import Loki from 'lokijs';
6
+ import * as FlexSearch from 'flexsearch';
7
+ import { promises as fs } from 'fs';
8
+ import path from 'path';
9
+ import { QueryParser } from './search/QueryParser.js';
10
+ // Using SearchOptions and SearchResult from types.ts
11
+ export class CodeIndexDB {
12
+ static instance;
13
+ db;
14
+ functionsCollection = null;
15
+ searchIndex; // FlexSearch Document instance
16
+ dbPath;
17
+ isInitialized = false;
18
+ constructor(dbPath = './.code-index/index.db') {
19
+ this.dbPath = dbPath;
20
+ this.db = new Loki(dbPath, {
21
+ autosave: true,
22
+ autosaveInterval: 4000,
23
+ autoload: false
24
+ });
25
+ // Initialize FlexSearch with full-text search configuration
26
+ const { Document } = FlexSearch;
27
+ this.searchIndex = new Document({
28
+ document: {
29
+ id: '$loki',
30
+ index: [
31
+ {
32
+ field: 'name',
33
+ tokenize: 'full',
34
+ optimize: true,
35
+ resolution: 9,
36
+ bidirectional: true,
37
+ weight: 10 // Highest weight for function name matches
38
+ },
39
+ {
40
+ field: 'tokenizedName',
41
+ tokenize: 'full',
42
+ optimize: true,
43
+ resolution: 9,
44
+ bidirectional: true,
45
+ weight: 9 // High weight for tokenized name matches
46
+ },
47
+ {
48
+ field: 'signature',
49
+ tokenize: 'full',
50
+ optimize: true,
51
+ resolution: 9,
52
+ weight: 8 // High weight for signature matches
53
+ },
54
+ {
55
+ field: 'purpose',
56
+ tokenize: 'full',
57
+ optimize: true,
58
+ resolution: 9,
59
+ weight: 7 // Important for semantic search
60
+ },
61
+ {
62
+ field: 'context',
63
+ tokenize: 'full',
64
+ optimize: true,
65
+ resolution: 9,
66
+ weight: 6 // Contextual information
67
+ },
68
+ {
69
+ field: 'jsDoc.description',
70
+ tokenize: 'full',
71
+ optimize: true,
72
+ resolution: 9,
73
+ weight: 5 // Documentation matches
74
+ },
75
+ {
76
+ field: 'parameters[].name',
77
+ tokenize: 'full',
78
+ optimize: true,
79
+ resolution: 7,
80
+ weight: 4 // Parameter name matches
81
+ },
82
+ {
83
+ field: 'parameters[].description',
84
+ tokenize: 'full',
85
+ optimize: true,
86
+ resolution: 7,
87
+ weight: 3 // Parameter description matches
88
+ },
89
+ {
90
+ field: 'returnType',
91
+ tokenize: 'full',
92
+ optimize: true,
93
+ resolution: 7,
94
+ weight: 2 // Return type matches
95
+ }
96
+ ]
97
+ },
98
+ tokenize: 'full', // Global full-text tokenization
99
+ optimize: true,
100
+ resolution: 9,
101
+ cache: 100,
102
+ context: {
103
+ depth: 3, // Enable contextual scoring
104
+ bidirectional: true, // Search in both directions
105
+ resolution: 9
106
+ },
107
+ threshold: 7, // Lower threshold for fuzzy matching
108
+ depth: 3 // Search depth for better fuzzy matching
109
+ });
110
+ }
111
+ static getInstance(dbPath) {
112
+ if (!CodeIndexDB.instance) {
113
+ CodeIndexDB.instance = new CodeIndexDB(dbPath || './.code-index/index.db');
114
+ }
115
+ return CodeIndexDB.instance;
116
+ }
117
+ async initialize() {
118
+ if (this.isInitialized)
119
+ return;
120
+ // Ensure directory exists
121
+ const dir = path.dirname(this.dbPath);
122
+ await fs.mkdir(dir, { recursive: true });
123
+ // Load database
124
+ await new Promise((resolve, reject) => {
125
+ this.db.loadDatabase({}, (err) => {
126
+ if (err)
127
+ reject(err);
128
+ else
129
+ resolve();
130
+ });
131
+ });
132
+ // Get or create functions collection
133
+ this.functionsCollection = this.db.getCollection('functions') ||
134
+ this.db.addCollection('functions', {
135
+ indices: ['name', 'filePath', 'language']
136
+ });
137
+ // Rebuild search index from existing data
138
+ const existingDocs = this.functionsCollection.find();
139
+ for (const doc of existingDocs) {
140
+ const normalizedData = this.normalizeFunctionData(doc);
141
+ this.searchIndex.add(normalizedData);
142
+ }
143
+ this.isInitialized = true;
144
+ }
145
+ ensureInitialized() {
146
+ if (!this.isInitialized || !this.functionsCollection) {
147
+ throw new Error('Database not initialized. Call initialize() first.');
148
+ }
149
+ }
150
+ /**
151
+ * Normalize function metadata for indexing
152
+ * Converts FunctionMetadata to have required fields for FlexSearch
153
+ */
154
+ normalizeFunctionData(func) {
155
+ // If it's already enhanced, return as-is with $loki if present
156
+ if ('signature' in func && 'parameters' in func) {
157
+ const normalized = { ...func };
158
+ // Ensure nested jsDoc structure exists
159
+ if (!normalized.jsDoc) {
160
+ normalized.jsDoc = { description: func.purpose || '' };
161
+ }
162
+ // Add tokenized name to improve searchability
163
+ normalized.tokenizedName = this.tokenizeFunctionName(func.name);
164
+ return normalized;
165
+ }
166
+ // Convert basic FunctionMetadata to have searchable fields
167
+ const enhanced = {
168
+ ...func,
169
+ signature: func.name, // Use name as signature for now
170
+ parameters: [], // Empty parameters array
171
+ jsDoc: {
172
+ description: func.purpose || ''
173
+ },
174
+ returnType: 'unknown',
175
+ // Add tokenized name for better searching
176
+ tokenizedName: this.tokenizeFunctionName(func.name)
177
+ };
178
+ return enhanced;
179
+ }
180
+ /**
181
+ * Tokenize function name for better search
182
+ * Splits camelCase, PascalCase, and snake_case into individual words
183
+ */
184
+ tokenizeFunctionName(name) {
185
+ // Remove class prefix if present (e.g., "UserService.authenticate" -> "authenticate")
186
+ const functionName = name.includes('.') ? name.split('.').pop() : name;
187
+ // Split camelCase and PascalCase
188
+ const camelSplit = functionName
189
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
190
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2');
191
+ // Split snake_case and kebab-case
192
+ const allSplit = camelSplit
193
+ .replace(/[_-]/g, ' ')
194
+ .toLowerCase()
195
+ .trim();
196
+ return allSplit;
197
+ }
198
+ async registerFunction(func) {
199
+ this.ensureInitialized();
200
+ try {
201
+ // Check if function already exists
202
+ const existing = this.functionsCollection.findOne({
203
+ name: func.name,
204
+ filePath: func.filePath
205
+ });
206
+ if (existing) {
207
+ // Update existing
208
+ Object.assign(existing, func);
209
+ this.functionsCollection.update(existing);
210
+ const normalizedData = this.normalizeFunctionData(existing);
211
+ this.searchIndex.update(normalizedData);
212
+ }
213
+ else {
214
+ // Insert new
215
+ const doc = this.functionsCollection.insert(func);
216
+ const normalizedData = this.normalizeFunctionData(doc);
217
+ this.searchIndex.add(normalizedData);
218
+ }
219
+ // Force save
220
+ this.db.saveDatabase();
221
+ }
222
+ catch (error) {
223
+ throw new Error(`Failed to register function: ${error instanceof Error ? error.message : 'Unknown error'}`);
224
+ }
225
+ }
226
+ async registerFunctions(functions) {
227
+ this.ensureInitialized();
228
+ let registered = 0;
229
+ let failed = 0;
230
+ const errors = [];
231
+ for (const func of functions) {
232
+ try {
233
+ await this.registerFunction(func);
234
+ registered++;
235
+ }
236
+ catch (error) {
237
+ failed++;
238
+ errors.push({
239
+ function: func.name || 'unknown',
240
+ error: error instanceof Error ? error.message : 'Unknown error'
241
+ });
242
+ }
243
+ }
244
+ return {
245
+ success: failed === 0,
246
+ registered,
247
+ failed,
248
+ errors: errors.length > 0 ? errors : undefined
249
+ };
250
+ }
251
+ /**
252
+ * Synchronize file index - add new functions, update existing, remove deleted
253
+ * @param filePath The file path to synchronize
254
+ * @param currentFunctions The current functions found in the file
255
+ * @returns Sync statistics
256
+ */
257
+ async syncFileIndex(filePath, currentFunctions) {
258
+ this.ensureInitialized();
259
+ const stats = {
260
+ added: 0,
261
+ updated: 0,
262
+ removed: 0
263
+ };
264
+ try {
265
+ // Get all currently indexed functions for this file
266
+ const indexedFunctions = this.functionsCollection.find({ filePath });
267
+ // Create a map of current functions by name for quick lookup
268
+ const currentFunctionMap = new Map(currentFunctions.map(f => [f.name, f]));
269
+ // Update or add functions
270
+ for (const func of currentFunctions) {
271
+ const existing = indexedFunctions.find(f => f.name === func.name);
272
+ if (existing) {
273
+ // Update existing function
274
+ Object.assign(existing, func);
275
+ this.functionsCollection.update(existing);
276
+ const normalizedData = this.normalizeFunctionData(existing);
277
+ this.searchIndex.update(normalizedData);
278
+ stats.updated++;
279
+ }
280
+ else {
281
+ // Add new function
282
+ const doc = this.functionsCollection.insert(func);
283
+ const normalizedData = this.normalizeFunctionData(doc);
284
+ this.searchIndex.add(normalizedData);
285
+ stats.added++;
286
+ }
287
+ }
288
+ // Remove functions that no longer exist in the file
289
+ for (const indexed of indexedFunctions) {
290
+ if (!currentFunctionMap.has(indexed.name)) {
291
+ // Function no longer exists, remove it
292
+ this.functionsCollection.remove(indexed);
293
+ this.searchIndex.remove(indexed.$loki);
294
+ stats.removed++;
295
+ }
296
+ }
297
+ // Force save
298
+ this.db.saveDatabase();
299
+ }
300
+ catch (error) {
301
+ throw new Error(`Failed to sync file index: ${error instanceof Error ? error.message : 'Unknown error'}`);
302
+ }
303
+ return stats;
304
+ }
305
+ async searchFunctions(options) {
306
+ this.ensureInitialized();
307
+ const startTime = Date.now();
308
+ let results = [];
309
+ let searchScores = new Map(); // Map of $loki id to relevance score
310
+ // Parse the query if provided
311
+ const queryParser = new QueryParser();
312
+ let parsedQuery;
313
+ if (options.query) {
314
+ parsedQuery = options.parsedQuery || queryParser.parse(options.query);
315
+ // Execute multi-strategy search
316
+ results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
317
+ }
318
+ else if (options.parsedQuery) {
319
+ parsedQuery = options.parsedQuery;
320
+ results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
321
+ }
322
+ else {
323
+ // No query, get all
324
+ results = this.functionsCollection.find();
325
+ // Give all results a base score
326
+ results.forEach((doc, index) => {
327
+ if (doc.$loki !== undefined) {
328
+ searchScores.set(doc.$loki, 50); // Base score for browse mode
329
+ }
330
+ });
331
+ }
332
+ // Apply filters from both options and parsedQuery
333
+ const combinedFilters = this.mergeFilters(options.filters, parsedQuery?.filters);
334
+ results = this.applyFilters(results, combinedFilters);
335
+ // Sort by relevance score
336
+ results.sort((a, b) => {
337
+ const scoreA = searchScores.get(a.$loki) || 0;
338
+ const scoreB = searchScores.get(b.$loki) || 0;
339
+ return scoreB - scoreA; // Higher scores first
340
+ });
341
+ // Apply pagination
342
+ const totalCount = results.length;
343
+ const limit = options.limit || 50;
344
+ const offset = options.offset || 0;
345
+ results = results.slice(offset, offset + limit);
346
+ // Clean up results and add scores
347
+ const functions = results.map((doc) => {
348
+ const { $loki, meta, ...functionData } = doc;
349
+ const score = searchScores.get($loki) || 0;
350
+ return {
351
+ ...functionData,
352
+ score
353
+ };
354
+ });
355
+ return {
356
+ functions,
357
+ totalCount,
358
+ query: options.query,
359
+ parsedQuery,
360
+ executionTime: Date.now() - startTime
361
+ };
362
+ }
363
+ /**
364
+ * Execute multi-strategy search with different approaches
365
+ * @param parsedQuery The parsed query object
366
+ * @param searchScores Map to store relevance scores
367
+ * @returns Array of matching documents
368
+ */
369
+ async executeMultiStrategySearch(parsedQuery, searchScores) {
370
+ const resultsMap = new Map(); // Map of $loki id to document
371
+ // Strategy 1: Exact phrase matching (highest weight)
372
+ if (parsedQuery.phrases.length > 0) {
373
+ for (const phrase of parsedQuery.phrases) {
374
+ const phraseResults = await this.searchWithFlexSearch(phrase, 1000);
375
+ this.mergeSearchResults(phraseResults, resultsMap, searchScores, 100); // High weight for exact phrases
376
+ }
377
+ }
378
+ // Strategy 2: All terms must match (AND logic) - use original terms
379
+ const originalTerms = parsedQuery.originalTerms || parsedQuery.terms;
380
+ if (originalTerms.length > 0) {
381
+ // For each original term, search including its synonyms
382
+ const termResultSets = new Map();
383
+ for (const originalTerm of originalTerms) {
384
+ // Get synonyms for this term
385
+ const queryParser = new QueryParser();
386
+ const synonyms = queryParser.getSynonyms(originalTerm);
387
+ const searchTerms = [originalTerm, ...synonyms];
388
+ // Find documents containing ANY of the synonyms
389
+ const termIds = new Set();
390
+ for (const searchTerm of searchTerms) {
391
+ const termResults = await this.searchWithFlexSearch(searchTerm, 1000);
392
+ termResults.forEach(doc => {
393
+ if (doc.$loki !== undefined)
394
+ termIds.add(doc.$loki);
395
+ });
396
+ }
397
+ termResultSets.set(originalTerm, termIds);
398
+ }
399
+ // Find documents that contain ALL original terms (or their synonyms)
400
+ if (termResultSets.size > 0) {
401
+ const allTermIds = this.intersectSets(Array.from(termResultSets.values()));
402
+ if (allTermIds.size > 0) {
403
+ const allTermDocs = this.functionsCollection.find({
404
+ $loki: { $in: Array.from(allTermIds) }
405
+ });
406
+ // Calculate score based on how many fields matched
407
+ allTermDocs.forEach(doc => {
408
+ const baseScore = 80; // Base score for AND matches
409
+ const fieldMatchBonus = this.calculateFieldMatchScore(doc, originalTerms);
410
+ this.mergeSearchResults([doc], resultsMap, searchScores, baseScore + fieldMatchBonus);
411
+ });
412
+ }
413
+ }
414
+ }
415
+ // Strategy 3: Any term can match (OR logic) - lower weight
416
+ if (parsedQuery.terms.length > 0) {
417
+ const orQuery = parsedQuery.terms.join(' ');
418
+ const orResults = await this.searchWithFlexSearch(orQuery, 1000);
419
+ // Score based on how many terms matched
420
+ orResults.forEach(doc => {
421
+ const matchCount = this.countMatchingTerms(doc, parsedQuery.terms);
422
+ const score = 40 + (matchCount * 10); // Base score + bonus per matched term
423
+ this.mergeSearchResults([doc], resultsMap, searchScores, score);
424
+ });
425
+ }
426
+ // Strategy 4: Fuzzy search if enabled
427
+ if (parsedQuery.fuzzy && parsedQuery.terms.length > 0) {
428
+ // FlexSearch already provides fuzzy matching with threshold setting
429
+ const fuzzyQuery = parsedQuery.terms.join(' ');
430
+ const fuzzyResults = await this.searchWithFlexSearch(fuzzyQuery, 1000, true);
431
+ this.mergeSearchResults(fuzzyResults, resultsMap, searchScores, 30); // Lower weight for fuzzy
432
+ }
433
+ // Apply excluded terms filter
434
+ let finalResults = Array.from(resultsMap.values());
435
+ if (parsedQuery.excludedTerms.length > 0) {
436
+ finalResults = this.excludeTermsFromResults(finalResults, parsedQuery.excludedTerms);
437
+ }
438
+ return finalResults;
439
+ }
440
+ /**
441
+ * Search using FlexSearch and return document results
442
+ */
443
+ async searchWithFlexSearch(query, limit, fuzzy = false) {
444
+ const searchOptions = { limit };
445
+ const searchResults = await this.searchIndex.search(query, searchOptions);
446
+ // Extract loki IDs from FlexSearch results
447
+ const lokiIds = [];
448
+ for (const fieldResult of searchResults) {
449
+ if (fieldResult.result && Array.isArray(fieldResult.result)) {
450
+ lokiIds.push(...fieldResult.result);
451
+ }
452
+ }
453
+ // Remove duplicates and fetch documents
454
+ const uniqueLokiIds = [...new Set(lokiIds)];
455
+ if (uniqueLokiIds.length === 0) {
456
+ return [];
457
+ }
458
+ return this.functionsCollection.find({
459
+ $loki: { $in: uniqueLokiIds }
460
+ });
461
+ }
462
+ /**
463
+ * Merge search results with scoring
464
+ */
465
+ mergeSearchResults(newResults, resultsMap, searchScores, weight) {
466
+ newResults.forEach(doc => {
467
+ if (doc.$loki === undefined)
468
+ return;
469
+ // Add or update document in results map
470
+ resultsMap.set(doc.$loki, doc);
471
+ // Update score (accumulate if already exists)
472
+ const currentScore = searchScores.get(doc.$loki) || 0;
473
+ searchScores.set(doc.$loki, currentScore + weight);
474
+ });
475
+ }
476
+ /**
477
+ * Calculate bonus score based on which fields matched
478
+ */
479
+ calculateFieldMatchScore(doc, terms) {
480
+ let score = 0;
481
+ const lowerTerms = terms.map(t => t.toLowerCase());
482
+ // Check each field with different weights
483
+ if (doc.name && lowerTerms.some(t => doc.name.toLowerCase().includes(t))) {
484
+ score += 15; // High bonus for name match
485
+ }
486
+ if (doc.signature && lowerTerms.some(t => doc.signature.toLowerCase().includes(t))) {
487
+ score += 10;
488
+ }
489
+ if (doc.purpose && lowerTerms.some(t => doc.purpose.toLowerCase().includes(t))) {
490
+ score += 8;
491
+ }
492
+ if (doc.jsDoc?.description && lowerTerms.some(t => doc.jsDoc.description.toLowerCase().includes(t))) {
493
+ score += 5;
494
+ }
495
+ return score;
496
+ }
497
+ /**
498
+ * Count how many terms match in a document
499
+ */
500
+ countMatchingTerms(doc, terms) {
501
+ let count = 0;
502
+ const documentText = this.getDocumentSearchText(doc).toLowerCase();
503
+ terms.forEach(term => {
504
+ if (documentText.includes(term.toLowerCase())) {
505
+ count++;
506
+ }
507
+ });
508
+ return count;
509
+ }
510
+ /**
511
+ * Get searchable text from document
512
+ */
513
+ getDocumentSearchText(doc) {
514
+ const parts = [
515
+ doc.name,
516
+ doc.signature,
517
+ doc.purpose,
518
+ doc.context,
519
+ doc.jsDoc?.description,
520
+ doc.returnType,
521
+ ...(doc.parameters || []).map(p => `${p.name} ${p.description || ''}`),
522
+ ...doc.dependencies
523
+ ];
524
+ return parts.filter(Boolean).join(' ');
525
+ }
526
+ /**
527
+ * Get synonyms for a term using QueryParser
528
+ */
529
+ getSynonyms(term) {
530
+ const queryParser = new QueryParser();
531
+ return queryParser.getSynonyms(term);
532
+ }
533
+ /**
534
+ * Exclude documents containing any of the excluded terms
535
+ */
536
+ excludeTermsFromResults(results, excludedTerms) {
537
+ return results.filter(doc => {
538
+ const searchText = this.getDocumentSearchText(doc).toLowerCase();
539
+ return !excludedTerms.some(term => searchText.includes(term.toLowerCase()));
540
+ });
541
+ }
542
+ /**
543
+ * Intersect multiple sets to find common elements
544
+ */
545
+ intersectSets(sets) {
546
+ if (sets.length === 0)
547
+ return new Set();
548
+ if (sets.length === 1)
549
+ return sets[0];
550
+ const result = new Set(sets[0]);
551
+ for (let i = 1; i < sets.length; i++) {
552
+ for (const item of result) {
553
+ if (!sets[i].has(item)) {
554
+ result.delete(item);
555
+ }
556
+ }
557
+ }
558
+ return result;
559
+ }
560
+ /**
561
+ * Merge filters from options and parsed query
562
+ */
563
+ mergeFilters(optionsFilters, queryFilters) {
564
+ const merged = {};
565
+ // Copy options filters
566
+ if (optionsFilters) {
567
+ Object.assign(merged, optionsFilters);
568
+ }
569
+ // Merge query filters (query filters take precedence)
570
+ if (queryFilters) {
571
+ if (queryFilters.language)
572
+ merged.language = queryFilters.language;
573
+ if (queryFilters.filePath)
574
+ merged.filePath = queryFilters.filePath;
575
+ if (queryFilters.fileType)
576
+ merged.fileType = queryFilters.fileType;
577
+ if (queryFilters.hasJsDoc !== undefined)
578
+ merged.hasJsDoc = queryFilters.hasJsDoc;
579
+ if (queryFilters.complexity)
580
+ merged.complexity = queryFilters.complexity;
581
+ if (queryFilters.dateRange)
582
+ merged.dateRange = queryFilters.dateRange;
583
+ }
584
+ return merged;
585
+ }
586
+ /**
587
+ * Apply filters to search results
588
+ */
589
+ applyFilters(results, filters) {
590
+ if (!filters)
591
+ return results;
592
+ let filtered = results;
593
+ if (filters.language) {
594
+ filtered = filtered.filter(doc => doc.language === filters.language);
595
+ }
596
+ if (filters.filePath) {
597
+ filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
598
+ }
599
+ if (filters.fileType) {
600
+ filtered = filtered.filter(doc => doc.filePath.endsWith(filters.fileType));
601
+ }
602
+ if (filters.hasJsDoc !== undefined) {
603
+ filtered = filtered.filter(doc => {
604
+ const hasJsDoc = doc.jsDoc && doc.jsDoc.description && doc.jsDoc.description.length > 0;
605
+ return filters.hasJsDoc ? hasJsDoc : !hasJsDoc;
606
+ });
607
+ }
608
+ if (filters.complexity) {
609
+ filtered = filtered.filter(doc => {
610
+ if (!doc.complexity)
611
+ return false;
612
+ const complexity = doc.complexity;
613
+ const min = filters.complexity.min || 0;
614
+ const max = filters.complexity.max || Infinity;
615
+ return complexity >= min && complexity <= max;
616
+ });
617
+ }
618
+ if (filters.hasAnyDependency && filters.hasAnyDependency.length > 0) {
619
+ filtered = filtered.filter(doc => filters.hasAnyDependency.some(dep => doc.dependencies.includes(dep)));
620
+ }
621
+ return filtered;
622
+ }
623
+ async findDefinition(name, filePath) {
624
+ this.ensureInitialized();
625
+ const query = { name };
626
+ if (filePath) {
627
+ query.filePath = filePath;
628
+ }
629
+ const result = this.functionsCollection.findOne(query);
630
+ if (!result)
631
+ return null;
632
+ const { $loki, meta, ...functionData } = result;
633
+ return functionData;
634
+ }
635
+ async getStats() {
636
+ this.ensureInitialized();
637
+ const allFunctions = this.functionsCollection.find();
638
+ // Calculate stats
639
+ const languages = {};
640
+ const dependencies = {};
641
+ const files = new Set();
642
+ for (const func of allFunctions) {
643
+ // Language stats
644
+ if (func.language) {
645
+ languages[func.language] = (languages[func.language] || 0) + 1;
646
+ }
647
+ // Dependency stats
648
+ for (const dep of func.dependencies) {
649
+ dependencies[dep] = (dependencies[dep] || 0) + 1;
650
+ }
651
+ // File stats
652
+ files.add(func.filePath);
653
+ }
654
+ // Top dependencies
655
+ const topDependencies = Object.entries(dependencies)
656
+ .sort((a, b) => b[1] - a[1])
657
+ .slice(0, 10)
658
+ .map(([name, count]) => ({ name, count }));
659
+ return {
660
+ totalFunctions: allFunctions.length,
661
+ languages,
662
+ topDependencies,
663
+ filesIndexed: files.size,
664
+ lastUpdated: new Date()
665
+ };
666
+ }
667
+ async clearIndex() {
668
+ this.ensureInitialized();
669
+ this.functionsCollection.clear();
670
+ this.searchIndex.clear();
671
+ this.db.saveDatabase();
672
+ }
673
+ async close() {
674
+ if (this.isInitialized) {
675
+ this.db.close();
676
+ this.isInitialized = false;
677
+ }
678
+ }
679
+ /**
680
+ * Synchronize a single file's functions with the index
681
+ * Adds new functions, updates existing ones, and removes deleted ones
682
+ */
683
+ async synchronizeFile(filePath) {
684
+ this.ensureInitialized();
685
+ const fs = require('fs').promises;
686
+ try {
687
+ // Check if file exists
688
+ await fs.access(filePath);
689
+ }
690
+ catch {
691
+ // File doesn't exist, remove all its functions
692
+ const existingFuncs = this.functionsCollection.find({ filePath });
693
+ for (const func of existingFuncs) {
694
+ this.functionsCollection.remove(func);
695
+ if (func.$loki !== undefined) {
696
+ await this.searchIndex.remove(func.$loki);
697
+ }
698
+ }
699
+ this.db.saveDatabase();
700
+ return { added: 0, updated: 0, removed: existingFuncs.length };
701
+ }
702
+ // Parse the file for functions
703
+ const { FunctionScanner } = await import('./functionScanner.js');
704
+ const scanner = new FunctionScanner();
705
+ const fileContent = await fs.readFile(filePath, 'utf-8');
706
+ const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'javascript';
707
+ const parsedFunctions = await scanner.scanFunctions(fileContent, filePath, language);
708
+ // Get existing functions
709
+ const existingFuncs = this.functionsCollection.find({ filePath });
710
+ const existingMap = new Map(existingFuncs.map(f => [f.name, f]));
711
+ const parsedMap = new Map(parsedFunctions.map(f => [f.name, f]));
712
+ let added = 0, updated = 0, removed = 0;
713
+ // Remove functions that no longer exist
714
+ for (const existing of existingFuncs) {
715
+ if (!parsedMap.has(existing.name)) {
716
+ this.functionsCollection.remove(existing);
717
+ if (existing.$loki !== undefined) {
718
+ await this.searchIndex.remove(existing.$loki);
719
+ }
720
+ removed++;
721
+ }
722
+ }
723
+ // Add or update functions
724
+ for (const parsed of parsedFunctions) {
725
+ const existing = existingMap.get(parsed.name);
726
+ if (existing) {
727
+ // Update existing
728
+ Object.assign(existing, parsed);
729
+ this.functionsCollection.update(existing);
730
+ const normalizedData = this.normalizeFunctionData(existing);
731
+ await this.searchIndex.update(normalizedData);
732
+ updated++;
733
+ }
734
+ else {
735
+ // Add new
736
+ await this.registerFunction(parsed);
737
+ added++;
738
+ }
739
+ }
740
+ if (added > 0 || updated > 0 || removed > 0) {
741
+ this.db.saveDatabase();
742
+ }
743
+ return { added, updated, removed };
744
+ }
745
+ /**
746
+ * Performs bulk cleanup of the index by verifying all indexed files still exist
747
+ * and removing entries for deleted files
748
+ */
749
+ async bulkCleanup() {
750
+ this.ensureInitialized();
751
+ const fs = require('fs').promises;
752
+ const allFunctions = this.functionsCollection.find();
753
+ const fileMap = new Map();
754
+ // Group functions by file
755
+ for (const func of allFunctions) {
756
+ if (!fileMap.has(func.filePath)) {
757
+ fileMap.set(func.filePath, []);
758
+ }
759
+ fileMap.get(func.filePath).push(func);
760
+ }
761
+ const removedFiles = [];
762
+ const errors = [];
763
+ let removedCount = 0;
764
+ // Check each file
765
+ for (const [filePath, functions] of fileMap) {
766
+ try {
767
+ await fs.access(filePath);
768
+ // File exists, no action needed
769
+ }
770
+ catch (error) {
771
+ // File doesn't exist, remove all its functions
772
+ for (const func of functions) {
773
+ this.functionsCollection.remove(func);
774
+ // Remove from search index
775
+ if (func.$loki !== undefined) {
776
+ await this.searchIndex.remove(func.$loki);
777
+ }
778
+ removedCount++;
779
+ }
780
+ removedFiles.push(filePath);
781
+ }
782
+ }
783
+ // Save changes
784
+ if (removedCount > 0) {
785
+ this.db.saveDatabase();
786
+ }
787
+ return {
788
+ scannedCount: fileMap.size,
789
+ removedCount,
790
+ removedFiles,
791
+ errors
792
+ };
793
+ }
794
+ /**
795
+ * Performs deep synchronization by re-scanning all indexed files
796
+ * Updates function signatures and removes stale entries
797
+ */
798
+ async deepSync(progressCallback) {
799
+ this.ensureInitialized();
800
+ const allFunctions = this.functionsCollection.find();
801
+ const fileMap = new Map();
802
+ // Group functions by file
803
+ for (const func of allFunctions) {
804
+ if (!fileMap.has(func.filePath)) {
805
+ fileMap.set(func.filePath, []);
806
+ }
807
+ fileMap.get(func.filePath).push(func);
808
+ }
809
+ let syncedFiles = 0;
810
+ let totalAdded = 0;
811
+ let totalUpdated = 0;
812
+ let totalRemoved = 0;
813
+ const errors = [];
814
+ let current = 0;
815
+ const total = fileMap.size;
816
+ // Sync each file
817
+ for (const [filePath, _] of fileMap) {
818
+ current++;
819
+ if (progressCallback) {
820
+ progressCallback({ current, total, file: filePath });
821
+ }
822
+ try {
823
+ const result = await this.synchronizeFile(filePath);
824
+ if (result) {
825
+ syncedFiles++;
826
+ totalAdded += result.added;
827
+ totalUpdated += result.updated;
828
+ totalRemoved += result.removed;
829
+ }
830
+ }
831
+ catch (error) {
832
+ errors.push({
833
+ file: filePath,
834
+ error: error instanceof Error ? error.message : String(error)
835
+ });
836
+ }
837
+ }
838
+ return {
839
+ syncedFiles,
840
+ addedFunctions: totalAdded,
841
+ updatedFunctions: totalUpdated,
842
+ removedFunctions: totalRemoved,
843
+ errors
844
+ };
845
+ }
846
+ }
847
+ //# sourceMappingURL=codeIndexDB.js.map