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.
- package/README.md +72 -2
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +229 -22
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDb.d.ts +150 -0
- package/dist/codeIndexDb.d.ts.map +1 -0
- package/dist/codeIndexDb.js +847 -0
- package/dist/codeIndexDb.js.map +1 -0
- package/dist/codeIndexService.d.ts +66 -0
- package/dist/codeIndexService.d.ts.map +1 -0
- package/dist/codeIndexService.js +201 -0
- package/dist/codeIndexService.js.map +1 -0
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +17 -1
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +9 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +9 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/functionScanner.d.ts +30 -0
- package/dist/functionScanner.d.ts.map +1 -0
- package/dist/functionScanner.js +264 -0
- package/dist/functionScanner.js.map +1 -0
- package/dist/generators/AWSQConfigGenerator.d.ts +8 -0
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -0
- package/dist/generators/AWSQConfigGenerator.js +33 -0
- package/dist/generators/AWSQConfigGenerator.js.map +1 -0
- package/dist/generators/AiderConfigGenerator.d.ts +8 -0
- package/dist/generators/AiderConfigGenerator.d.ts.map +1 -0
- package/dist/generators/AiderConfigGenerator.js +23 -0
- package/dist/generators/AiderConfigGenerator.js.map +1 -0
- package/dist/generators/BaseConfigGenerator.d.ts +51 -0
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -0
- package/dist/generators/BaseConfigGenerator.js +67 -0
- package/dist/generators/BaseConfigGenerator.js.map +1 -0
- package/dist/generators/ClaudeConfigGenerator.d.ts +14 -0
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -0
- package/dist/generators/ClaudeConfigGenerator.js +70 -0
- package/dist/generators/ClaudeConfigGenerator.js.map +1 -0
- package/dist/generators/ClineConfigGenerator.d.ts +9 -0
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -0
- package/dist/generators/ClineConfigGenerator.js +26 -0
- package/dist/generators/ClineConfigGenerator.js.map +1 -0
- package/dist/generators/CodeiumConfigGenerator.d.ts +8 -0
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -0
- package/dist/generators/CodeiumConfigGenerator.js +30 -0
- package/dist/generators/CodeiumConfigGenerator.js.map +1 -0
- package/dist/generators/ConfigGeneratorFactory.d.ts +35 -0
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -0
- package/dist/generators/ConfigGeneratorFactory.js +77 -0
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -0
- package/dist/generators/ContinueConfigGenerator.d.ts +12 -0
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -0
- package/dist/generators/ContinueConfigGenerator.js +99 -0
- package/dist/generators/ContinueConfigGenerator.js.map +1 -0
- package/dist/generators/CopilotConfigGenerator.d.ts +12 -0
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -0
- package/dist/generators/CopilotConfigGenerator.js +77 -0
- package/dist/generators/CopilotConfigGenerator.js.map +1 -0
- package/dist/generators/CursorConfigGenerator.d.ts +12 -0
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -0
- package/dist/generators/CursorConfigGenerator.js +80 -0
- package/dist/generators/CursorConfigGenerator.js.map +1 -0
- package/dist/generators/JetBrainsConfigGenerator.d.ts +8 -0
- package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -0
- package/dist/generators/JetBrainsConfigGenerator.js +36 -0
- package/dist/generators/JetBrainsConfigGenerator.js.map +1 -0
- package/dist/generators/VSCodeConfigGenerator.d.ts +8 -0
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -0
- package/dist/generators/VSCodeConfigGenerator.js +29 -0
- package/dist/generators/VSCodeConfigGenerator.js.map +1 -0
- package/dist/generators/index.d.ts +13 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +14 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/mcp-standalone.js +0 -0
- package/dist/mcp.js +483 -2
- package/dist/mcp.js.map +1 -1
- package/dist/search/QueryParser.d.ts +60 -0
- package/dist/search/QueryParser.d.ts.map +1 -0
- package/dist/search/QueryParser.js +330 -0
- package/dist/search/QueryParser.js.map +1 -0
- package/dist/search/index.d.ts +7 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +6 -0
- package/dist/search/index.js.map +1 -0
- package/dist/test-multi-strategy-search.d.ts +3 -0
- package/dist/test-multi-strategy-search.d.ts.map +1 -0
- package/dist/test-multi-strategy-search.js +170 -0
- package/dist/test-multi-strategy-search.js.map +1 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts +37 -0
- package/dist/ui/InteractivePrompts.d.ts.map +1 -0
- package/dist/ui/InteractivePrompts.js +147 -0
- package/dist/ui/InteractivePrompts.js.map +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/utils/jsDocParser.d.ts +84 -0
- package/dist/utils/jsDocParser.d.ts.map +1 -0
- package/dist/utils/jsDocParser.js +366 -0
- package/dist/utils/jsDocParser.js.map +1 -0
- 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
|