@zuvia-software-solutions/code-mapper 2.6.0 → 2.6.1

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.
@@ -766,6 +766,42 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
766
766
  }
767
767
  list.push(call);
768
768
  }
769
+ // Pre-build indexes for O(1) lookups in the hot loop.
770
+ // Without these, interface dispatch does graph.relationships.filter() per call = O(calls × edges).
771
+ const implementsIndex = new Map(); // interfaceNodeId → [classNodeId, ...]
772
+ const hasMethodIndex = new Map(); // classNodeId → methods
773
+ const reverseImportIndex = new Map(); // importedFile → [importingFile, ...]
774
+ for (const rel of graph.iterRelationships()) {
775
+ if (rel.type === 'IMPLEMENTS') {
776
+ let arr = implementsIndex.get(rel.targetId);
777
+ if (!arr) {
778
+ arr = [];
779
+ implementsIndex.set(rel.targetId, arr);
780
+ }
781
+ arr.push(rel.sourceId);
782
+ }
783
+ else if (rel.type === 'HAS_METHOD') {
784
+ const methodNode = graph.getNode(rel.targetId);
785
+ if (methodNode) {
786
+ let arr = hasMethodIndex.get(rel.sourceId);
787
+ if (!arr) {
788
+ arr = [];
789
+ hasMethodIndex.set(rel.sourceId, arr);
790
+ }
791
+ arr.push({ targetId: rel.targetId, name: methodNode.properties.name });
792
+ }
793
+ }
794
+ }
795
+ for (const [file, importedFiles] of ctx.importMap) {
796
+ for (const imported of importedFiles) {
797
+ let arr = reverseImportIndex.get(imported);
798
+ if (!arr) {
799
+ arr = [];
800
+ reverseImportIndex.set(imported, arr);
801
+ }
802
+ arr.push(file);
803
+ }
804
+ }
769
805
  const totalFiles = byFile.size;
770
806
  let filesProcessed = 0;
771
807
  for (const [filePath, calls] of byFile) {
@@ -825,38 +861,29 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
825
861
  const interfaceDefs = receiverResolved.candidates.filter(d => d.type === 'Interface' || d.type === 'Trait');
826
862
  if (interfaceDefs.length > 0) {
827
863
  for (const ifaceDef of interfaceDefs) {
828
- // Strategy 1: Class-based — find IMPLEMENTS edges
829
- const implRows = graph.relationships.filter(r => r.type === 'IMPLEMENTS' && r.targetId === toNodeId(ifaceDef.nodeId));
830
- for (const implRel of implRows) {
831
- const methodEdges = graph.relationships.filter(r => r.type === 'HAS_METHOD' && r.sourceId === implRel.sourceId);
832
- for (const methodEdge of methodEdges) {
833
- const methodNode = graph.getNode(methodEdge.targetId);
834
- if (methodNode && methodNode.properties.name === effectiveCall.calledName) {
835
- resolved = { nodeId: methodEdge.targetId, confidence: 0.85, reason: 'interface-dispatch' };
836
- break;
864
+ // Strategy 1: Class-based — find IMPLEMENTS edges via pre-built index
865
+ const implClassIds = implementsIndex.get(toNodeId(ifaceDef.nodeId));
866
+ if (implClassIds) {
867
+ for (const classId of implClassIds) {
868
+ const methods = hasMethodIndex.get(classId);
869
+ if (methods) {
870
+ const match = methods.find(m => m.name === effectiveCall.calledName);
871
+ if (match) {
872
+ resolved = { nodeId: match.targetId, confidence: 0.85, reason: 'interface-dispatch' };
873
+ break;
874
+ }
837
875
  }
838
876
  }
839
- if (resolved)
840
- break;
841
877
  }
842
878
  if (resolved)
843
879
  break;
844
880
  // Strategy 2: Factory/closure pattern — find functions returning this interface
845
- // e.g. createEventBus(): EventBus → the closure has emit/subscribe as inner functions
846
- // Look for the method name in files that import the interface's file
847
881
  if (!resolved) {
848
882
  const methodName = effectiveCall.calledName;
849
883
  const ifaceFile = ifaceDef.filePath;
850
- // Check: which files import the interface's file?
851
- const importingFiles = [];
852
- for (const [file, importedFiles] of ctx.importMap) {
853
- if (importedFiles.has(ifaceFile)) {
854
- importingFiles.push(file);
855
- }
856
- }
884
+ const importingFiles = reverseImportIndex.get(ifaceFile) ?? [];
857
885
  // Also check the interface's own file
858
- importingFiles.push(ifaceFile);
859
- for (const file of importingFiles) {
886
+ for (const file of [...importingFiles, ifaceFile]) {
860
887
  const method = ctx.symbols.lookupExactFull(file, methodName);
861
888
  if (method && (method.type === 'Function' || method.type === 'Method')) {
862
889
  resolved = { nodeId: method.nodeId, confidence: 0.75, reason: 'interface-factory-dispatch' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuvia-software-solutions/code-mapper",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",