code-auditor-mcp 1.3.1 → 1.6.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 (48) hide show
  1. package/README.md +133 -2
  2. package/dist/analyzers/solidAnalyzer.d.ts +5 -1
  3. package/dist/analyzers/solidAnalyzer.d.ts.map +1 -1
  4. package/dist/analyzers/solidAnalyzer.js +174 -2
  5. package/dist/analyzers/solidAnalyzer.js.map +1 -1
  6. package/dist/codeIndexDb.d.ts +47 -0
  7. package/dist/codeIndexDb.d.ts.map +1 -1
  8. package/dist/codeIndexDb.js +432 -5
  9. package/dist/codeIndexDb.js.map +1 -1
  10. package/dist/codeIndexService.d.ts +26 -0
  11. package/dist/codeIndexService.d.ts.map +1 -1
  12. package/dist/codeIndexService.js +60 -0
  13. package/dist/codeIndexService.js.map +1 -1
  14. package/dist/functionScanner.d.ts.map +1 -1
  15. package/dist/functionScanner.js +76 -6
  16. package/dist/functionScanner.js.map +1 -1
  17. package/dist/mcp-standalone.js +14 -4
  18. package/dist/mcp-standalone.js.map +1 -1
  19. package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
  20. package/dist/mcp-tools/workflowGuide.js +45 -0
  21. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  22. package/dist/mcp.js +3 -3
  23. package/dist/mcp.js.map +1 -1
  24. package/dist/search/QueryParser.d.ts +2 -0
  25. package/dist/search/QueryParser.d.ts.map +1 -1
  26. package/dist/search/QueryParser.js +191 -26
  27. package/dist/search/QueryParser.js.map +1 -1
  28. package/dist/types.d.ts +135 -0
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/types.js +16 -1
  31. package/dist/types.js.map +1 -1
  32. package/dist/utils/astUtils.d.ts +17 -1
  33. package/dist/utils/astUtils.d.ts.map +1 -1
  34. package/dist/utils/astUtils.js +121 -0
  35. package/dist/utils/astUtils.js.map +1 -1
  36. package/dist/utils/componentPatterns.d.ts +22 -0
  37. package/dist/utils/componentPatterns.d.ts.map +1 -0
  38. package/dist/utils/componentPatterns.js +273 -0
  39. package/dist/utils/componentPatterns.js.map +1 -0
  40. package/dist/utils/componentResponsibility.d.ts +39 -0
  41. package/dist/utils/componentResponsibility.d.ts.map +1 -0
  42. package/dist/utils/componentResponsibility.js +487 -0
  43. package/dist/utils/componentResponsibility.js.map +1 -0
  44. package/dist/utils/dependencyExtractor.d.ts +30 -0
  45. package/dist/utils/dependencyExtractor.d.ts.map +1 -0
  46. package/dist/utils/dependencyExtractor.js +237 -0
  47. package/dist/utils/dependencyExtractor.js.map +1 -0
  48. package/package.json +16 -15
@@ -92,6 +92,13 @@ export class CodeIndexDB {
92
92
  optimize: true,
93
93
  resolution: 7,
94
94
  weight: 2 // Return type matches
95
+ },
96
+ {
97
+ field: 'body',
98
+ tokenize: 'full',
99
+ optimize: true,
100
+ resolution: 9,
101
+ weight: 1 // Body content matches (lower weight to prioritize metadata matches)
95
102
  }
96
103
  ]
97
104
  },
@@ -173,7 +180,9 @@ export class CodeIndexDB {
173
180
  },
174
181
  returnType: 'unknown',
175
182
  // Add tokenized name for better searching
176
- tokenizedName: this.tokenizeFunctionName(func.name)
183
+ tokenizedName: this.tokenizeFunctionName(func.name),
184
+ // Extract body from metadata if present
185
+ body: func.metadata?.body
177
186
  };
178
187
  return enhanced;
179
188
  }
@@ -294,6 +303,8 @@ export class CodeIndexDB {
294
303
  stats.removed++;
295
304
  }
296
305
  }
306
+ // Update dependency graph for the affected functions
307
+ await this.updateDependencyGraph(filePath);
297
308
  // Force save
298
309
  this.db.saveDatabase();
299
310
  }
@@ -302,6 +313,192 @@ export class CodeIndexDB {
302
313
  }
303
314
  return stats;
304
315
  }
316
+ /**
317
+ * Update dependency graph - build reverse mappings for calledBy relationships
318
+ * @param filePath Optional file path to limit the update scope
319
+ */
320
+ async updateDependencyGraph(filePath) {
321
+ this.ensureInitialized();
322
+ try {
323
+ // Get all functions (or just those in the specified file)
324
+ const functions = filePath
325
+ ? this.functionsCollection.find({ filePath })
326
+ : this.functionsCollection.find();
327
+ // Clear existing calledBy relationships for affected functions
328
+ for (const func of functions) {
329
+ if (func.metadata) {
330
+ func.metadata.calledBy = [];
331
+ }
332
+ }
333
+ // Build calledBy relationships by iterating through all functions
334
+ const allFunctions = this.functionsCollection.find();
335
+ for (const caller of allFunctions) {
336
+ if (caller.metadata?.functionCalls) {
337
+ for (const callee of caller.metadata.functionCalls) {
338
+ // Find the called function
339
+ const calledFunc = this.findFunctionByQualifiedName(callee);
340
+ if (calledFunc && calledFunc.metadata) {
341
+ if (!calledFunc.metadata.calledBy) {
342
+ calledFunc.metadata.calledBy = [];
343
+ }
344
+ // Add caller to calledBy list if not already present
345
+ const callerName = this.getQualifiedFunctionName(caller);
346
+ if (!calledFunc.metadata.calledBy.includes(callerName)) {
347
+ calledFunc.metadata.calledBy.push(callerName);
348
+ this.functionsCollection.update(calledFunc);
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+ catch (error) {
356
+ throw new Error(`Failed to update dependency graph: ${error instanceof Error ? error.message : 'Unknown error'}`);
357
+ }
358
+ }
359
+ /**
360
+ * Find a function by its qualified name (e.g., "filePath#functionName")
361
+ */
362
+ findFunctionByQualifiedName(qualifiedName) {
363
+ // Handle different name formats
364
+ if (qualifiedName.includes('#')) {
365
+ const [filePath, functionName] = qualifiedName.split('#');
366
+ return this.functionsCollection.findOne({
367
+ filePath: { $regex: filePath },
368
+ name: functionName
369
+ });
370
+ }
371
+ else {
372
+ // Simple function name - search across all files
373
+ return this.functionsCollection.findOne({ name: qualifiedName });
374
+ }
375
+ }
376
+ /**
377
+ * Get qualified name for a function
378
+ */
379
+ getQualifiedFunctionName(func) {
380
+ return `${func.filePath}#${func.name}`;
381
+ }
382
+ /**
383
+ * Get transitive dependencies for a function (functions it calls, directly and indirectly)
384
+ * @param functionName The function to analyze
385
+ * @param maxDepth Maximum depth to traverse (default 10)
386
+ * @returns Array of function names with their depth
387
+ */
388
+ async getTransitiveDependencies(functionName, maxDepth = 10) {
389
+ this.ensureInitialized();
390
+ const dependencies = [];
391
+ const visited = new Set();
392
+ const traverse = (funcName, depth) => {
393
+ if (depth > maxDepth || visited.has(funcName))
394
+ return;
395
+ visited.add(funcName);
396
+ const func = this.findFunctionByQualifiedName(funcName);
397
+ if (!func?.metadata?.functionCalls)
398
+ return;
399
+ for (const callee of func.metadata.functionCalls) {
400
+ if (!visited.has(callee)) {
401
+ dependencies.push({ name: callee, depth });
402
+ traverse(callee, depth + 1);
403
+ }
404
+ }
405
+ };
406
+ traverse(functionName, 1);
407
+ return dependencies;
408
+ }
409
+ /**
410
+ * Get transitive callers for a function (functions that call it, directly and indirectly)
411
+ * @param functionName The function to analyze
412
+ * @param maxDepth Maximum depth to traverse (default 10)
413
+ * @returns Array of function names with their depth
414
+ */
415
+ async getTransitiveCallers(functionName, maxDepth = 10) {
416
+ this.ensureInitialized();
417
+ const callers = [];
418
+ const visited = new Set();
419
+ const traverse = (funcName, depth) => {
420
+ if (depth > maxDepth || visited.has(funcName))
421
+ return;
422
+ visited.add(funcName);
423
+ const func = this.findFunctionByQualifiedName(funcName);
424
+ if (!func?.metadata?.calledBy)
425
+ return;
426
+ for (const caller of func.metadata.calledBy) {
427
+ if (!visited.has(caller)) {
428
+ callers.push({ name: caller, depth });
429
+ traverse(caller, depth + 1);
430
+ }
431
+ }
432
+ };
433
+ traverse(functionName, 1);
434
+ return callers;
435
+ }
436
+ /**
437
+ * Detect circular dependencies in the codebase
438
+ * @returns Array of circular dependency chains
439
+ */
440
+ async detectCircularDependencies() {
441
+ this.ensureInitialized();
442
+ const cycles = [];
443
+ const allFunctions = this.functionsCollection.find();
444
+ for (const func of allFunctions) {
445
+ if (!func.metadata?.functionCalls)
446
+ continue;
447
+ const funcName = this.getQualifiedFunctionName(func);
448
+ const visited = new Set();
449
+ const path = [];
450
+ const hasCycle = (current) => {
451
+ if (path.includes(current)) {
452
+ // Found a cycle - extract it
453
+ const cycleStart = path.indexOf(current);
454
+ const cycle = [...path.slice(cycleStart), current];
455
+ // Check if we already have this cycle (in any rotation)
456
+ const isNewCycle = !cycles.some(existing => existing.length === cycle.length &&
457
+ existing.every(f => cycle.includes(f)));
458
+ if (isNewCycle) {
459
+ cycles.push(cycle);
460
+ }
461
+ return true;
462
+ }
463
+ if (visited.has(current))
464
+ return false;
465
+ visited.add(current);
466
+ path.push(current);
467
+ const currentFunc = this.findFunctionByQualifiedName(current);
468
+ if (currentFunc?.metadata?.functionCalls) {
469
+ for (const callee of currentFunc.metadata.functionCalls) {
470
+ if (hasCycle(callee))
471
+ return true;
472
+ }
473
+ }
474
+ path.pop();
475
+ return false;
476
+ };
477
+ hasCycle(funcName);
478
+ }
479
+ return cycles;
480
+ }
481
+ /**
482
+ * Calculate the maximum dependency depth for each function
483
+ * Updates the dependencyDepth field in metadata
484
+ */
485
+ async calculateDependencyDepths() {
486
+ this.ensureInitialized();
487
+ const allFunctions = this.functionsCollection.find();
488
+ for (const func of allFunctions) {
489
+ const funcName = this.getQualifiedFunctionName(func);
490
+ const dependencies = await this.getTransitiveDependencies(funcName);
491
+ const maxDepth = dependencies.length > 0
492
+ ? Math.max(...dependencies.map(d => d.depth))
493
+ : 0;
494
+ if (!func.metadata) {
495
+ func.metadata = {};
496
+ }
497
+ func.metadata.dependencyDepth = maxDepth;
498
+ this.functionsCollection.update(func);
499
+ }
500
+ this.db.saveDatabase();
501
+ }
305
502
  async searchFunctions(options) {
306
503
  this.ensureInitialized();
307
504
  const startTime = Date.now();
@@ -310,12 +507,41 @@ export class CodeIndexDB {
310
507
  // Parse the query if provided
311
508
  const queryParser = new QueryParser();
312
509
  let parsedQuery;
510
+ // Determine search mode
511
+ const searchMode = options.searchMode || 'metadata';
313
512
  if (options.query) {
314
513
  parsedQuery = options.parsedQuery || queryParser.parse(options.query);
315
514
  // Check if there are search terms or just filters
316
515
  if (parsedQuery.terms.length > 0 || parsedQuery.phrases.length > 0) {
317
- // Execute multi-strategy search
318
- results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
516
+ if (searchMode === 'content') {
517
+ // Content search only
518
+ results = await this.executeContentSearch(parsedQuery, searchScores);
519
+ }
520
+ else if (searchMode === 'both') {
521
+ // Both metadata and content search
522
+ const metadataResults = await this.executeMultiStrategySearch(parsedQuery, searchScores);
523
+ const contentResults = await this.executeContentSearch(parsedQuery, searchScores);
524
+ // Merge results, combining scores for duplicates
525
+ const resultMap = new Map();
526
+ metadataResults.forEach(doc => {
527
+ if (doc.$loki !== undefined) {
528
+ resultMap.set(doc.$loki, doc);
529
+ }
530
+ });
531
+ contentResults.forEach(doc => {
532
+ if (doc.$loki !== undefined) {
533
+ resultMap.set(doc.$loki, doc);
534
+ // Combine scores
535
+ const existingScore = searchScores.get(doc.$loki) || 0;
536
+ searchScores.set(doc.$loki, existingScore);
537
+ }
538
+ });
539
+ results = Array.from(resultMap.values());
540
+ }
541
+ else {
542
+ // Metadata search only (default)
543
+ results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
544
+ }
319
545
  }
320
546
  else {
321
547
  // No search terms, just filters - get all
@@ -331,7 +557,32 @@ export class CodeIndexDB {
331
557
  parsedQuery = options.parsedQuery;
332
558
  // Check if there are search terms or just filters
333
559
  if (parsedQuery.terms.length > 0 || parsedQuery.phrases.length > 0) {
334
- results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
560
+ if (searchMode === 'content') {
561
+ results = await this.executeContentSearch(parsedQuery, searchScores);
562
+ }
563
+ else if (searchMode === 'both') {
564
+ const metadataResults = await this.executeMultiStrategySearch(parsedQuery, searchScores);
565
+ const contentResults = await this.executeContentSearch(parsedQuery, searchScores);
566
+ // Merge results
567
+ const resultMap = new Map();
568
+ metadataResults.forEach(doc => {
569
+ if (doc.$loki !== undefined) {
570
+ resultMap.set(doc.$loki, doc);
571
+ }
572
+ });
573
+ contentResults.forEach(doc => {
574
+ if (doc.$loki !== undefined) {
575
+ resultMap.set(doc.$loki, doc);
576
+ // Combine scores
577
+ const existingScore = searchScores.get(doc.$loki) || 0;
578
+ searchScores.set(doc.$loki, existingScore);
579
+ }
580
+ });
581
+ results = Array.from(resultMap.values());
582
+ }
583
+ else {
584
+ results = await this.executeMultiStrategySearch(parsedQuery, searchScores);
585
+ }
335
586
  }
336
587
  else {
337
588
  // No search terms, just filters - get all
@@ -620,7 +871,25 @@ export class CodeIndexDB {
620
871
  filtered = filtered.filter(doc => doc.language === filters.language);
621
872
  }
622
873
  if (filters.filePath) {
623
- filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
874
+ // Support both exact match and includes based on the filter format
875
+ if (filters.filePath.includes('*') || filters.filePath.includes('?')) {
876
+ // Glob pattern - convert to regex
877
+ const pattern = filters.filePath
878
+ .replace(/\*/g, '.*')
879
+ .replace(/\?/g, '.')
880
+ .replace(/\//g, '\\/');
881
+ const regex = new RegExp(pattern);
882
+ filtered = filtered.filter(doc => regex.test(doc.filePath));
883
+ }
884
+ else if (filters.filePath.endsWith('.ts') || filters.filePath.endsWith('.tsx') ||
885
+ filters.filePath.endsWith('.js') || filters.filePath.endsWith('.jsx')) {
886
+ // If it looks like a full filename, match the end of the path
887
+ filtered = filtered.filter(doc => doc.filePath.endsWith(filters.filePath));
888
+ }
889
+ else {
890
+ // Otherwise, do substring match (for directory paths)
891
+ filtered = filtered.filter(doc => doc.filePath.includes(filters.filePath));
892
+ }
624
893
  }
625
894
  if (filters.fileType) {
626
895
  filtered = filtered.filter(doc => doc.filePath.endsWith(filters.fileType));
@@ -677,6 +946,52 @@ export class CodeIndexDB {
677
946
  else if (filters.metadata.hasProp) {
678
947
  return false; // No props but filter requires one
679
948
  }
949
+ // Check usesDependency
950
+ if (filters.metadata.usesDependency) {
951
+ const dep = filters.metadata.usesDependency.toLowerCase();
952
+ // Check in both file-level dependencies and function-specific usedImports
953
+ const usesDepInFile = doc.dependencies.some(d => d.toLowerCase().includes(dep));
954
+ const usesDepInFunction = doc.metadata.usedImports?.some(imp => imp.toLowerCase().includes(dep)) || false;
955
+ if (!usesDepInFile && !usesDepInFunction)
956
+ return false;
957
+ }
958
+ // Check callsFunction
959
+ if (filters.metadata.callsFunction && doc.metadata.functionCalls) {
960
+ const targetFunc = filters.metadata.callsFunction.toLowerCase();
961
+ const callsFunc = doc.metadata.functionCalls.some(call => call.toLowerCase().includes(targetFunc));
962
+ if (!callsFunc)
963
+ return false;
964
+ }
965
+ else if (filters.metadata.callsFunction) {
966
+ return false; // No function calls but filter requires one
967
+ }
968
+ // Check calledByFunction
969
+ if (filters.metadata.calledByFunction && doc.metadata.calledBy) {
970
+ const callerFunc = filters.metadata.calledByFunction.toLowerCase();
971
+ const isCalledBy = doc.metadata.calledBy.some(caller => caller.toLowerCase().includes(callerFunc));
972
+ if (!isCalledBy)
973
+ return false;
974
+ }
975
+ else if (filters.metadata.calledByFunction) {
976
+ return false; // Not called by any function but filter requires one
977
+ }
978
+ // Check dependsOnModule
979
+ if (filters.metadata.dependsOnModule) {
980
+ const module = filters.metadata.dependsOnModule.toLowerCase();
981
+ // Check if any import or function call references this module
982
+ const dependsOnFile = doc.filePath.toLowerCase().includes(module);
983
+ const dependsOnImport = doc.dependencies.some(dep => dep.toLowerCase().includes(module));
984
+ const dependsOnCall = doc.metadata.functionCalls?.some(call => call.toLowerCase().includes(module)) || false;
985
+ if (!dependsOnFile && !dependsOnImport && !dependsOnCall)
986
+ return false;
987
+ }
988
+ // Check hasUnusedImports
989
+ if (filters.metadata.hasUnusedImports) {
990
+ const hasUnused = doc.metadata.unusedImports &&
991
+ doc.metadata.unusedImports.length > 0;
992
+ if (!hasUnused)
993
+ return false;
994
+ }
680
995
  return true;
681
996
  });
682
997
  }
@@ -905,5 +1220,117 @@ export class CodeIndexDB {
905
1220
  errors
906
1221
  };
907
1222
  }
1223
+ /**
1224
+ * Execute content search - search within function bodies
1225
+ */
1226
+ async executeContentSearch(parsedQuery, searchScores) {
1227
+ const resultsMap = new Map();
1228
+ // Get all functions (we'll filter them)
1229
+ const allFunctions = this.functionsCollection.find();
1230
+ // Search in function bodies
1231
+ for (const doc of allFunctions) {
1232
+ // Check for body in multiple possible locations
1233
+ const body = doc.body || doc.metadata?.body;
1234
+ if (!body)
1235
+ continue;
1236
+ let score = 0;
1237
+ const bodyLower = body.toLowerCase();
1238
+ const matches = [];
1239
+ // Split body into lines for line-level matching
1240
+ const lines = body.split('\n');
1241
+ // Check for exact phrases
1242
+ for (const phrase of parsedQuery.phrases) {
1243
+ const phraseLower = phrase.toLowerCase();
1244
+ lines.forEach((line, lineIndex) => {
1245
+ const lineLower = line.toLowerCase();
1246
+ let columnIndex = lineLower.indexOf(phraseLower);
1247
+ while (columnIndex !== -1) {
1248
+ matches.push({
1249
+ term: phrase,
1250
+ line: (doc.lineNumber || 0) + lineIndex,
1251
+ column: columnIndex + 1
1252
+ });
1253
+ score += 100; // High score for exact phrase match
1254
+ columnIndex = lineLower.indexOf(phraseLower, columnIndex + 1);
1255
+ }
1256
+ });
1257
+ }
1258
+ // Check for individual terms
1259
+ for (const term of parsedQuery.terms) {
1260
+ const termLower = term.toLowerCase();
1261
+ lines.forEach((line, lineIndex) => {
1262
+ const lineLower = line.toLowerCase();
1263
+ let columnIndex = lineLower.indexOf(termLower);
1264
+ while (columnIndex !== -1) {
1265
+ matches.push({
1266
+ term: term,
1267
+ line: (doc.lineNumber || 0) + lineIndex,
1268
+ column: columnIndex + 1
1269
+ });
1270
+ score += 20; // Score for term match
1271
+ columnIndex = lineLower.indexOf(termLower, columnIndex + 1);
1272
+ }
1273
+ });
1274
+ }
1275
+ // Check for excluded terms
1276
+ let excluded = false;
1277
+ for (const excludedTerm of parsedQuery.excludedTerms) {
1278
+ if (bodyLower.includes(excludedTerm.toLowerCase())) {
1279
+ excluded = true;
1280
+ break;
1281
+ }
1282
+ }
1283
+ // Add to results if score > 0 and not excluded
1284
+ if (score > 0 && !excluded && doc.$loki !== undefined) {
1285
+ // Store match information in metadata
1286
+ const resultDoc = { ...doc };
1287
+ if (!resultDoc.metadata)
1288
+ resultDoc.metadata = {};
1289
+ resultDoc.metadata.contentMatches = matches;
1290
+ // Add match context (surrounding lines)
1291
+ const contextLines = 2; // Number of lines before and after to include
1292
+ const matchContexts = [];
1293
+ // Group matches by line to avoid duplicate context
1294
+ const matchesByLine = new Map();
1295
+ for (const match of matches) {
1296
+ const relativeLineNum = match.line - (doc.lineNumber || 0);
1297
+ if (!matchesByLine.has(relativeLineNum)) {
1298
+ matchesByLine.set(relativeLineNum, []);
1299
+ }
1300
+ matchesByLine.get(relativeLineNum).push(match);
1301
+ }
1302
+ // Build context for each unique line
1303
+ for (const [relativeLineNum, lineMatches] of matchesByLine) {
1304
+ if (relativeLineNum >= 0 && relativeLineNum < lines.length) {
1305
+ const before = [];
1306
+ const after = [];
1307
+ // Get lines before
1308
+ for (let i = Math.max(0, relativeLineNum - contextLines); i < relativeLineNum; i++) {
1309
+ before.push(lines[i]);
1310
+ }
1311
+ // Get lines after
1312
+ for (let i = relativeLineNum + 1; i < Math.min(lines.length, relativeLineNum + contextLines + 1); i++) {
1313
+ after.push(lines[i]);
1314
+ }
1315
+ // Add context for each match on this line
1316
+ for (const match of lineMatches) {
1317
+ matchContexts.push({
1318
+ match,
1319
+ context: {
1320
+ before,
1321
+ line: lines[relativeLineNum],
1322
+ after
1323
+ }
1324
+ });
1325
+ }
1326
+ }
1327
+ }
1328
+ resultDoc.metadata.matchContexts = matchContexts;
1329
+ resultsMap.set(doc.$loki, resultDoc);
1330
+ searchScores.set(doc.$loki, score);
1331
+ }
1332
+ }
1333
+ return Array.from(resultsMap.values());
1334
+ }
908
1335
  }
909
1336
  //# sourceMappingURL=codeIndexDB.js.map