diff --git a/mcp_codebase.py b/mcp_codebase.py index 34c9d4e..d4d6123 100644 --- a/mcp_codebase.py +++ b/mcp_codebase.py @@ -2864,11 +2864,12 @@ def build_indexes(): interface_registry = {} # TypeScript/Java interfaces trait_registry = {} # Rust traits class_registry = {} # Classes across all languages + package_registry = {} # Packages/modules across languages logger.info("=== Phase 1: Creating nodes ===") # ------------------------------------------------- - # PHASE 1: Create all nodes + # PHASE 1: Create all nodes with rich metadata # ------------------------------------------------- for m in metadatas: fpath = m.get("file") @@ -2919,7 +2920,7 @@ def build_indexes(): # Store for second-pass processing created_nodes[node_id] = base_attrs.copy() created_nodes[node_id]["type"] = node_type - created_nodes[node_id]["metadata"] = m # Keep full metadata for second pass + created_nodes[node_id]["metadata"] = m # ===== FUNCTIONS & METHODS ===== if node_type in ["function", "method", "async_function", "constructor"]: @@ -2932,7 +2933,6 @@ def build_indexes(): created_nodes[node_id]["class_name"] = class_name created_nodes[node_id]["metadata"] = m - # Enhanced method attributes graph.add_node( node_id, **base_attrs, @@ -2944,15 +2944,15 @@ def build_indexes(): is_static=m.get("is_static", False), is_abstract=m.get("is_abstract", False), is_async=m.get("is_async", False), + is_generator=m.get("is_generator", False), decorators=m.get("decorators", []), annotations=m.get("annotations", []) ) - # Connect method to class class_node_id = f"{lang}::class::{fpath}::{class_name}" graph.add_edge(class_node_id, node_id, "contains") else: - # Regular function or async function + # Regular function, async function, or constructor graph.add_node( node_id, **base_attrs, @@ -2963,8 +2963,10 @@ def build_indexes(): is_async=m.get("is_async", False), is_generator=m.get("is_generator", False), is_exported=m.get("is_exported", False), + is_property=m.get("is_property", False), decorators=m.get("decorators", []), - annotations=m.get("annotations", []) + annotations=m.get("annotations", []), + generics=m.get("generics", []) ) graph.add_edge(file_node_id, node_id, "contains") @@ -2982,15 +2984,18 @@ def build_indexes(): fields=m.get("fields", []), static_methods=m.get("static_methods", []), class_methods=m.get("class_methods", []), + class_variables=m.get("class_variables", []), visibility=m.get("visibility"), is_abstract=m.get("is_abstract", False), + is_final=m.get("is_final", False), decorators=m.get("decorators", []), - annotations=m.get("annotations", []) + annotations=m.get("annotations", []), + generics=m.get("generics", []), + metaclass=m.get("metaclass") ) graph.add_edge(file_node_id, node_id, "contains") - # Register class for cross-references class_registry[name] = { "node_id": node_id, "lang": lang, @@ -2998,7 +3003,7 @@ def build_indexes(): "metadata": m } - # ===== STRUCTS ===== + # ===== STRUCTS (Rust/Go) ===== elif node_type == "struct": graph.add_node( node_id, @@ -3012,7 +3017,6 @@ def build_indexes(): graph.add_edge(file_node_id, node_id, "contains") - # Register for type relationships class_registry[name] = { "node_id": node_id, "lang": lang, @@ -3020,7 +3024,7 @@ def build_indexes(): "metadata": m } - # ===== INTERFACES ===== + # ===== INTERFACES (TypeScript/Java/Go) ===== elif node_type == "interface": graph.add_node( node_id, @@ -3029,13 +3033,13 @@ def build_indexes(): extends=m.get("extends", []), methods=m.get("methods", []), properties=m.get("properties", []), + interface_methods=m.get("interface_methods", []), visibility=m.get("visibility"), generics=m.get("generics", []) ) graph.add_edge(file_node_id, node_id, "contains") - # Register interface interface_registry[name] = { "node_id": node_id, "lang": lang, @@ -3043,7 +3047,7 @@ def build_indexes(): "metadata": m } - # ===== TRAITS ===== + # ===== TRAITS (Rust) ===== elif node_type == "trait": graph.add_node( node_id, @@ -3056,7 +3060,6 @@ def build_indexes(): graph.add_edge(file_node_id, node_id, "contains") - # Register trait trait_registry[name] = { "node_id": node_id, "lang": lang, @@ -3072,7 +3075,8 @@ def build_indexes(): type=node_type, traits=m.get("traits", []), methods=m.get("methods", []), - generics=m.get("generics", []) + generics=m.get("generics", []), + is_unsafe=m.get("is_unsafe", False) ) graph.add_edge(file_node_id, node_id, "contains") @@ -3085,6 +3089,7 @@ def build_indexes(): type=node_type, variants=m.get("variants", []), members=m.get("members", []), + fields=m.get("fields", []), # For Rust enum variants with fields visibility=m.get("visibility") ) @@ -3102,6 +3107,42 @@ def build_indexes(): graph.add_edge(file_node_id, node_id, "contains") + # ===== MODULES (Rust/Go/Python packages) ===== + elif node_type == "module": + graph.add_node( + node_id, + **base_attrs, + type=node_type, + visibility=m.get("visibility") + ) + graph.add_edge(file_node_id, node_id, "contains") + + # Create symbolic module node for cross-references + module_symbol_id = f"module_symbol::{lang}::{name}" + if module_symbol_id not in graph.graph.nodes: + graph.add_node( + module_symbol_id, + type=name, # Use module name as type + name=name, + file=fpath, + lang=lang + ) + created_nodes[module_symbol_id] = { + "type": name, + "name": name, + "file": fpath, + "lang": lang + } + + graph.add_edge(node_id, module_symbol_id, "defines") + + package_registry[name] = { + "node_id": node_id, + "symbol_id": module_symbol_id, + "lang": lang, + "file": fpath + } + # ===== SVELTE COMPONENTS ===== elif node_type == "markup" and lang == "svelte": component_name = m.get("component_name", name) @@ -3117,12 +3158,15 @@ def build_indexes(): has_default_slot=m.get("has_default_slot", False), events=m.get("event_handlers", []), used_components=m.get("used_components", []), - stores=m.get("stores", []) + stores=m.get("stores", []), + template_stores=m.get("template_stores", []), + use_directives=m.get("use_directives", []), + transitions=m.get("transitions", []), + bind_directives=m.get("bind_directives", []) ) graph.add_edge(file_node_id, component_node_id, "contains") - # Register component component_registry[component_name] = { "node_id": component_node_id, "file": fpath, @@ -3131,8 +3175,13 @@ def build_indexes(): node_id = component_node_id + # ===== SVELTE SCRIPTS ===== + elif node_type in ["script", "script_module"] and lang in ["typescript", "javascript"]: + # These are handled by their contained declarations + pass + # ===== VARIABLES & CONSTANTS ===== - elif node_type in ["variable", "constant", "prop", "reactive"]: + elif node_type in ["variable", "constant", "prop", "reactive", "var", "const"]: graph.add_node( node_id, **base_attrs, @@ -3141,12 +3190,13 @@ def build_indexes(): var_kind=m.get("var_kind"), # const, let, var is_exported=m.get("is_exported", False), is_reactive=m.get("is_reactive", False), - is_store=m.get("is_store", False) + is_store=m.get("is_store", False), + value=m.get("value") ) graph.add_edge(file_node_id, node_id, "contains") - # ===== STYLES ===== + # ===== STYLES (CSS/SCSS/LESS) ===== elif node_type == "style": graph.add_node( node_id, @@ -3155,15 +3205,20 @@ def build_indexes(): css_classes=m.get("css_classes", []), css_ids=m.get("css_ids", []), css_variables=m.get("css_variables", []), - is_scoped=m.get("is_scoped", True) + css_selectors=m.get("css_selectors", []), + is_scoped=m.get("is_scoped", True), + component_name=m.get("component_name") ) graph.add_edge(file_node_id, node_id, "contains") # ===== IMPORTS ===== elif node_type == "import": - # Create import node - import_path = m.get("import_path") or m.get("full_name") or name + import_path = m.get("import_path") or m.get("full_name") or m.get("parameters", [name])[0] if m.get("parameters") else name + + if isinstance(import_path, list): + import_path = import_path[0] if import_path else name + import_node_id = f"{lang}::import::{fpath}::{import_path}" graph.add_node( @@ -3173,8 +3228,37 @@ def build_indexes(): import_path=import_path, imported_names=m.get("imported_names", []) ) + graph.add_edge(file_node_id, import_node_id, "contains") - graph.add_edge(file_node_id, import_node_id, "imports") + # Create symbolic nodes for imports (rich graph structure) + _create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_path, lang, created_nodes) + + node_id = import_node_id + + # ===== FIELDS (Java standalone fields) ===== + elif node_type == "field": + graph.add_node( + node_id, + **base_attrs, + type=node_type, + field_type=m.get("field_type"), + visibility=m.get("visibility"), + is_static=m.get("is_static", False), + is_final=m.get("is_final", False), + is_volatile=m.get("is_volatile", False), + annotations=m.get("annotations", []) + ) + graph.add_edge(file_node_id, node_id, "contains") + + # ===== ANNOTATIONS (Java) ===== + elif node_type == "annotation": + graph.add_node( + node_id, + **base_attrs, + type=node_type, + visibility=m.get("visibility") + ) + graph.add_edge(file_node_id, node_id, "contains") # ===== GENERIC FALLBACK ===== else: @@ -3184,6 +3268,7 @@ def build_indexes(): logger.info(f"Phase 1 complete: Created {len(created_nodes)} nodes") logger.info(f" Classes: {len(class_registry)}, Interfaces: {len(interface_registry)}") logger.info(f" Traits: {len(trait_registry)}, Components: {len(component_registry)}") + logger.info(f" Packages/Modules: {len(package_registry)}") # ------------------------------------------------- # PHASE 2: Build relationships @@ -3198,9 +3283,13 @@ def build_indexes(): node_type = node_data.get("type") fpath = node_data.get("file") lang = node_data.get("lang") + name = node_data.get("name") # ===== IMPORTS & DEPENDENCIES ===== imports_raw = m.get("imports", []) + if node_type == "import" and "parameters" in m: + imports_raw = m["parameters"] + if isinstance(imports_raw, str): imports = [imp.strip() for imp in imports_raw.split(",") if imp.strip()] else: @@ -3208,7 +3297,7 @@ def build_indexes(): if imports: file_node_id = f"file::{fpath}" - graph._add_import_edges(file_node_id, imports) + _add_import_edges_enhanced(graph, file_node_id, node_id, imports, lang, created_nodes) # ===== FUNCTION CALLS ===== calls_raw = m.get("calls", []) @@ -3220,22 +3309,37 @@ def build_indexes(): if calls: graph._add_call_edges(node_id, calls, fpath) - # ===== CLASS INHERITANCE ===== - if node_type == "class": + # ===== CLASS/STRUCT INHERITANCE ===== + if node_type in ["class", "struct"]: # Extends relationships extends = m.get("extends") if extends: - for parent_name, parent_info in class_registry.items(): - if parent_name == extends: - graph.add_edge(node_id, parent_info["node_id"], "extends") + if isinstance(extends, list): + extends_list = extends + else: + extends_list = [extends] + + for parent_name in extends_list: + for class_name, class_info in class_registry.items(): + if class_name == parent_name: + graph.add_edge(node_id, class_info["node_id"], "extends") # Implements relationships implements = m.get("implements", []) + if isinstance(implements, str): + implements = [implements] + for interface_name in implements: + # Check interface registry for iface_name, iface_info in interface_registry.items(): if iface_name == interface_name: graph.add_edge(node_id, iface_info["node_id"], "implements") + # Check trait registry (Rust) + for trait_name, trait_info in trait_registry.items(): + if trait_name == interface_name: + graph.add_edge(node_id, trait_info["node_id"], "implements") + # ===== INTERFACE INHERITANCE ===== if node_type == "interface": extends_list = m.get("extends", []) @@ -3260,18 +3364,28 @@ def build_indexes(): graph.add_edge(node_id, t_info["node_id"], "implements") # Link to struct/type being implemented + impl_name = m.get("name", "") for class_name, class_info in class_registry.items(): - if class_name in m.get("name", ""): + if class_name == impl_name or class_name in impl_name: graph.add_edge(node_id, class_info["node_id"], "implements_for") + # ===== GO METHOD RECEIVERS ===== + if lang == "go" and node_type in ["method", "function"]: + receiver = m.get("receiver") + receiver_base = m.get("receiver_base_type") + + if receiver_base: + # Find the struct this method belongs to + for struct_name, struct_info in class_registry.items(): + if struct_name == receiver_base and struct_info["lang"] == "go": + graph.add_edge(struct_info["node_id"], node_id, "has_method") + # ===== METHOD RETURN TYPES & PARAMETERS ===== - if node_type in ["function", "method", "async_function"]: + if node_type in ["function", "method", "async_function", "constructor"]: return_type = m.get("return_type") - if return_type and return_type not in ['void', '()', 'Result', 'None', '', 'any']: - # Clean up return type + if return_type and return_type not in ['void', '()', 'Result', 'None', '', 'any', 'unknown']: clean_return = _clean_type_string(return_type) - # Find matching type definition for type_name, type_info in {**class_registry, **interface_registry, **trait_registry}.items(): if type_name == clean_return: graph.add_edge(node_id, type_info["node_id"], "returns") @@ -3282,12 +3396,12 @@ def build_indexes(): for param in parameters: param_type = _extract_param_type(param) if param_type: - for type_name, type_info in {**class_registry, **interface_registry}.items(): + for type_name, type_info in {**class_registry, **interface_registry, **trait_registry}.items(): if type_name == param_type: graph.add_edge(node_id, type_info["node_id"], "uses_parameter") # ===== SVELTE COMPONENT RELATIONSHIPS ===== - if node_type == "markup" and lang == "svelte": + if node_type == "component" or (node_type == "markup" and lang == "svelte"): # Link to used components used_components = m.get("used_components", []) for comp_name in used_components: @@ -3295,17 +3409,25 @@ def build_indexes(): comp_info = component_registry[comp_name] graph.add_edge(node_id, comp_info["node_id"], "uses_component") + # Link props to type definitions + props = m.get("component_props", []) + for prop_name in props: + # Try to find corresponding interface/type + for iface_name, iface_info in interface_registry.items(): + if f"{m.get('component_name')}Props" == iface_name: + graph.add_edge(node_id, iface_info["node_id"], "uses_props_type") + # ===== FIELD TYPE RELATIONSHIPS ===== if node_type in ["struct", "class"]: fields = m.get("fields", []) for field in fields: field_type = _extract_field_type(field) if field_type: - for type_name, type_info in {**class_registry, **interface_registry}.items(): + for type_name, type_info in {**class_registry, **interface_registry, **trait_registry}.items(): if type_name == field_type: graph.add_edge(node_id, type_info["node_id"], "has_field_type") - logger.info("Phase 2 complete: Built relationships") + logger.info("Phase 2 complete: Built intra-language relationships") # ------------------------------------------------- # PHASE 3: Advanced relationship building @@ -3330,7 +3452,7 @@ def build_indexes(): graph.save() graph.to_json() graph.to_toon() - logger.info(f"Graph built with {graph.graph.number_of_nodes()} nodes and {graph.graph.number_of_edges()} edges.") + logger.info(f"✅ Graph built with {graph.graph.number_of_nodes()} nodes and {graph.graph.number_of_edges()} edges.") # ------------------------------------------------- # PHASE 4: Embedding/index pipeline @@ -3340,15 +3462,12 @@ def build_indexes(): os.environ["OLLAMA_HOST"] = OLLAMA_BASE_URL embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL, base_url=OLLAMA_BASE_URL) - # Generate base embeddings in batches logger.info(f"Generating embeddings for {len(texts)} documents in batches of {EMBEDDING_BATCH_SIZE}...") base_embeddings = batch_embed_documents(texts) - # Apply contextual weights logger.info("Applying contextual weights...") weighted_embeddings = apply_contextual_weights_to_embeddings(base_embeddings, metadatas) - # Create vector store with weighted embeddings logger.info("Creating vector store (Chroma) with weighted embeddings...") class WeightedEmbeddingFunction: @@ -3377,14 +3496,12 @@ def build_indexes(): metadatas=metadatas ) - # Build BM25 index logger.info("Building BM25 index...") tokenized = [t.lower().split() for t in texts] bm25 = BM25Okapi(tokenized) bm25_corpus = texts chunks_metadata = metadatas - # Save BM25 index and metadata BM25_INDEX_PATH.write_text( json.dumps({"corpus": texts, "metadata": metadatas}, indent=2), encoding="utf-8" @@ -3394,61 +3511,223 @@ def build_indexes(): elapsed = index_build_time - start_time logger.info(f"✅ Index build complete. Total time: {elapsed:.1f}s") - # ------------------------------------------------- # Helper Functions # ------------------------------------------------- +def _add_import_edges_enhanced(graph, file_node_id, importing_node_id, imports, lang, created_nodes): + """Enhanced import edge creation that creates rich symbolic nodes for all languages.""" + for imp_path in imports: + if not imp_path or not isinstance(imp_path, str): + continue + + # Normalize path separators based on language + if lang == "python": + normalized_path = imp_path.replace('.', '::') + elif lang in ["javascript", "typescript", "svelte"]: + # Skip very short relative imports + if imp_path.startswith('.') and len(imp_path) < 3: + continue + normalized_path = imp_path.replace('/', '::').replace('@', '') + elif lang == "rust": + normalized_path = imp_path + elif lang == "go": + normalized_path = imp_path.replace('/', '::') + elif lang == "java": + normalized_path = imp_path.replace('.', '::') + else: + normalized_path = imp_path.replace('.', '::').replace('/', '::') + + parts = [p.strip() for p in normalized_path.split('::') if p.strip()] + if not parts: + continue + + # Create package/crate/module hierarchy + package_name = parts[0] + package_node_id = f"package::{lang}::{package_name}" + + if package_node_id not in graph.graph.nodes: + graph.add_node( + package_node_id, + type=package_name, # Rich type name + name=package_name, + file=imp_path, + lang=lang, + is_external=True + ) + created_nodes[package_node_id] = { + "type": package_name, + "name": package_name, + "file": imp_path, + "lang": lang + } + + # Connect file to package + if not graph.graph.has_edge(file_node_id, package_node_id): + graph.add_edge(file_node_id, package_node_id, "imports") + + # Build hierarchical module structure + current_path = package_name + parent_node = package_node_id + + for i, part in enumerate(parts[1:], 1): + current_path = f"{current_path}::{part}" + + # Determine node type based on position and context + if i == len(parts) - 1: + # Last part - could be a class, function, or module + node_type_name = part + else: + # Intermediate part - likely a module/namespace + node_type_name = part + + module_node_id = f"symbol::{lang}::{current_path}" + + if module_node_id not in graph.graph.nodes: + graph.add_node( + module_node_id, + type=node_type_name, # Rich type name + name=current_path, + file=imp_path, + lang=lang, + is_symbol=True + ) + created_nodes[module_node_id] = { + "type": node_type_name, + "name": current_path, + "file": imp_path, + "lang": lang + } + + # Connect to parent + if not graph.graph.has_edge(parent_node, module_node_id): + graph.add_edge(parent_node, module_node_id, "contains") + + parent_node = module_node_id + + # Connect the importing node to the final symbol + if parent_node != package_node_id: + if not graph.graph.has_edge(importing_node_id, parent_node): + graph.add_edge(importing_node_id, parent_node, "imports") + + def _clean_type_string(type_str: str) -> str: - """Clean up type strings for matching.""" + """Clean up type strings for matching across all languages.""" if not type_str: return "" - # Remove common type wrappers type_str = type_str.strip() - type_str = re.sub(r'Option<(.+)>', r'\1', type_str) - type_str = re.sub(r'Result<(.+?)(?:,|>).*', r'\1', type_str) - type_str = re.sub(r'Vec<(.+)>', r'\1', type_str) - type_str = re.sub(r'Box<(.+)>', r'\1', type_str) - type_str = re.sub(r'Arc<(.+)>', r'\1', type_str) - type_str = re.sub(r'Rc<(.+)>', r'\1', type_str) - type_str = re.sub(r'Promise<(.+)>', r'\1', type_str) - type_str = re.sub(r'Array<(.+)>', r'\1', type_str) - # Remove references and mutability + # Remove common Rust wrappers + type_str = re.sub(r'Option<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Result<(.+?)(?:,|>).*', r'\1', type_str) + type_str = re.sub(r'Vec<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Box<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Arc<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Rc<(.+?)>', r'\1', type_str) + + # Remove TypeScript/JavaScript wrappers + type_str = re.sub(r'Promise<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Array<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Observable<(.+?)>', r'\1', type_str) + + # Remove Java wrappers + type_str = re.sub(r'Optional<(.+?)>', r'\1', type_str) + type_str = re.sub(r'List<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Set<(.+?)>', r'\1', type_str) + type_str = re.sub(r'Map<.+?,\s*(.+?)>', r'\1', type_str) + + # Remove Go slices and pointers + type_str = re.sub(r'\[\](.+)', r'\1', type_str) + type_str = re.sub(r'\*(.+)', r'\1', type_str) + + # Remove Rust references and mutability type_str = type_str.replace('&', '').replace('mut ', '').strip() - # Remove generics for simpler matching - type_str = re.sub(r'<.*>', '', type_str).strip() + # Remove generic parameters for simpler matching + type_str = re.sub(r'<.*?>', '', type_str).strip() + + # Remove array brackets + type_str = re.sub(r'\[.*?\]', '', type_str).strip() return type_str def _extract_param_type(param: str) -> str: - """Extract type from parameter string.""" - if not param or ':' not in param: + """Extract type from parameter string for all languages.""" + if not param or not isinstance(param, str): return "" - parts = param.split(':') - if len(parts) < 2: + # Handle different parameter formats + # Python: "name: Type" or "name" + # Rust: "name: Type" or "Type" + # Go: "name: Type" or "name Type" + # Java/TypeScript: "Type name" or "name: Type" + + if ':' in param: + # Python, Rust, TypeScript style + parts = param.split(':') + if len(parts) < 2: + return "" + param_type = parts[-1].strip() + elif ' ' in param: + # Go, Java style + parts = param.strip().split() + # Check if first part looks like a type (starts with uppercase or is a keyword) + if parts[0][0].isupper() or parts[0] in ['int', 'string', 'bool', 'float', 'double', 'long']: + param_type = parts[0] + else: + # Assume last part is the type + param_type = parts[-1] + else: return "" - param_type = parts[-1].strip() + # Clean up type annotations and default values + param_type = param_type.split('=')[0].strip() # Remove default values + param_type = param_type.split('`')[0].strip() # Remove Go struct tags + return _clean_type_string(param_type) def _extract_field_type(field: str) -> str: - """Extract type from field string.""" - if not field or ':' not in field: + """Extract type from field string for all languages.""" + if not field or not isinstance(field, str): return "" - parts = field.split(':') - if len(parts) < 2: + # Handle different field formats + # Python: "name: Type" + # Rust: "name: Type" or "embedded:Type" + # Go: "name Type" or "name Type `tag`" + # Java: "Type name" + # TypeScript: "name: Type" + + # Handle embedded fields (Rust/Go) + if field.startswith('embedded:'): + return _clean_type_string(field[9:]) + + if ':' in field: + # Python, Rust, TypeScript style + parts = field.split(':') + if len(parts) < 2: + return "" + field_type = parts[-1].strip() + elif ' ' in field: + # Go, Java style + parts = field.strip().split() + if len(parts) < 2: + return "" + # Check if first part looks like a type + if parts[0][0].isupper() or parts[0] in ['int', 'string', 'bool', 'float', 'double', 'long']: + field_type = parts[0] + else: + field_type = parts[1] if len(parts) > 1 else parts[0] + else: return "" - field_type = parts[-1].strip() - # Remove struct tags for languages like Go - field_type = re.sub(r'`.*`', '', field_type).strip() + # Remove struct tags, JSON tags, etc. + field_type = re.sub(r'`.*?`', '', field_type).strip() + field_type = re.sub(r'".*?"', '', field_type).strip() + return _clean_type_string(field_type) @@ -3457,164 +3736,323 @@ def build_cross_language_relationships(graph, created_nodes, component_registry, """Build relationships across different programming languages.""" connections = 0 - # Example: Connect TypeScript interfaces to Rust structs with same name + # TypeScript/Java interfaces <-> Rust structs with matching names for ts_name, ts_info in interface_registry.items(): - if ts_info["lang"] == "typescript": + if ts_info["lang"] in ["typescript", "javascript"]: for rust_name, rust_info in class_registry.items(): if rust_info["lang"] == "rust" and ts_name == rust_name: - graph.add_edge(ts_info["node_id"], rust_info["node_id"], "mirrors") + if not graph.graph.has_edge(ts_info["node_id"], rust_info["node_id"]): + graph.add_edge(ts_info["node_id"], rust_info["node_id"], "mirrors") + connections += 1 + + # Java classes <-> Go structs with matching names + for java_name, java_info in class_registry.items(): + if java_info["lang"] == "java": + for go_name, go_info in class_registry.items(): + if go_info["lang"] == "go" and java_name == go_name: + if not graph.graph.has_edge(java_info["node_id"], go_info["node_id"]): + graph.add_edge(java_info["node_id"], go_info["node_id"], "mirrors") + connections += 1 + + # Svelte components to TypeScript type definitions + for comp_name, comp_info in component_registry.items(): + # Look for matching Props interface + props_name = f"{comp_name}Props" + for iface_name, iface_info in interface_registry.items(): + if iface_name == props_name or iface_name == comp_name: + if not graph.graph.has_edge(comp_info["node_id"], iface_info["node_id"]): + graph.add_edge(comp_info["node_id"], iface_info["node_id"], "uses_type") connections += 1 - # Connect Svelte components to their TypeScript definitions - for comp_name, comp_info in component_registry.items(): + # Look for matching class for class_name, class_info in class_registry.items(): - if class_name == comp_name or class_name == f"{comp_name}Props": - graph.add_edge(comp_info["node_id"], class_info["node_id"], "uses_type") - connections += 1 + if class_name == comp_name or class_name == props_name: + if not graph.graph.has_edge(comp_info["node_id"], class_info["node_id"]): + graph.add_edge(comp_info["node_id"], class_info["node_id"], "uses_type") + connections += 1 + + # Python classes <-> TypeScript interfaces (common in web backends) + for py_name, py_info in class_registry.items(): + if py_info["lang"] == "python": + for ts_name, ts_info in interface_registry.items(): + if ts_info["lang"] == "typescript" and py_name == ts_name: + if not graph.graph.has_edge(py_info["node_id"], ts_info["node_id"]): + graph.add_edge(py_info["node_id"], ts_info["node_id"], "api_type") + connections += 1 + + # Rust traits <-> Go/Java interfaces (behavioral contracts) + for trait_name, trait_info in interface_registry.items(): + if trait_info.get("lang") == "rust": + for iface_name, iface_info in interface_registry.items(): + if iface_info["lang"] in ["go", "java"] and trait_name == iface_name: + if not graph.graph.has_edge(trait_info["node_id"], iface_info["node_id"]): + graph.add_edge(trait_info["node_id"], iface_info["node_id"], "similar_contract") + connections += 1 return connections + def build_type_relationships(graph, created_nodes): - """Build type relationships between nodes (parameter types, return types, etc.)""" + """Build enhanced type relationships within language boundaries.""" connections = 0 + # Track all type definitions by name for quick lookup + type_map = {} for node_id, node_data in created_nodes.items(): node_type = node_data.get("type") + name = node_data.get("name") + lang = node_data.get("lang") - # Handle function/method parameter and return types - if node_type in ["function", "method", "constructor"]: - connections += _connect_function_types(graph, node_id, node_data) + if node_type in ["class", "struct", "interface", "trait", "enum", "type"] and name: + key = f"{lang}::{name}" + if key not in type_map: + type_map[key] = [] + type_map[key].append(node_id) - # Handle class/struct inheritance and implementation - elif node_type in ["class", "struct", "interface"]: - connections += _connect_class_relationships(graph, node_id, node_data) + # Build relationships based on type usage + for node_id, node_data in created_nodes.items(): + m = node_data.get("metadata", {}) + if not m: + continue - # Handle impl blocks - elif node_type == "impl": - connections += _connect_impl_relationships(graph, node_id, node_data) + lang = node_data.get("lang") + node_type = node_data.get("type") + + # Field types + fields = m.get("fields", []) + if isinstance(fields, list): + for field in fields: + field_type = _extract_field_type(field) + if field_type: + type_key = f"{lang}::{field_type}" + if type_key in type_map: + for target_id in type_map[type_key]: + if not graph.graph.has_edge(node_id, target_id): + graph.add_edge(node_id, target_id, "has_field_of_type") + connections += 1 + + # Return types + return_type = m.get("return_type") + if return_type: + clean_return = _clean_type_string(return_type) + if clean_return: + type_key = f"{lang}::{clean_return}" + if type_key in type_map: + for target_id in type_map[type_key]: + if not graph.graph.has_edge(node_id, target_id): + graph.add_edge(node_id, target_id, "returns_type") + connections += 1 + + # Parameter types + parameters = m.get("parameters", []) + if isinstance(parameters, list): + for param in parameters: + param_type = _extract_param_type(param) + if param_type: + type_key = f"{lang}::{param_type}" + if type_key in type_map: + for target_id in type_map[type_key]: + if not graph.graph.has_edge(node_id, target_id): + graph.add_edge(node_id, target_id, "accepts_type") + connections += 1 + + # Generic type parameters + generics = m.get("generics", []) + if isinstance(generics, list): + for generic in generics: + # Generics might have constraints (e.g., T: Trait) + generic_parts = generic.split(':') + if len(generic_parts) > 1: + constraint = generic_parts[1].strip() + type_key = f"{lang}::{constraint}" + if type_key in type_map: + for target_id in type_map[type_key]: + if not graph.graph.has_edge(node_id, target_id): + graph.add_edge(node_id, target_id, "constrained_by") + connections += 1 return connections -def _connect_function_types(graph, node_id, node_data): - """Connect function nodes to their parameter and return types""" - connections = 0 - - # Connect return type - return_type = node_data.get("return_type") - if return_type and _is_meaningful_type(return_type): - return_type_clean = _clean_type_name(return_type) - for type_id, type_data in graph.find_nodes(name=return_type_clean): - graph.add_edge(node_id, type_id, "returns") - connections += 1 - - # Connect parameter types - parameters = node_data.get("parameters", []) - for param in parameters: - param_type = _extract_parameter_type(param) - if param_type and _is_meaningful_type(param_type): - param_type_clean = _clean_type_name(param_type) - for type_id, type_data in graph.find_nodes(name=param_type_clean): - graph.add_edge(node_id, type_id, "uses_parameter") - connections += 1 - - return connections - -def _connect_class_relationships(graph, node_id, node_data): - """Connect class/struct nodes to parent classes and interfaces""" - connections = 0 - - # Inheritance - extends = node_data.get("extends") - if extends and _is_meaningful_type(extends): - for parent_id, parent_data in graph.find_nodes(name=extends): - graph.add_edge(node_id, parent_id, "extends") - connections += 1 - - # Interface implementation - implements = node_data.get("implements", []) - for interface in implements: - if _is_meaningful_type(interface): - for interface_id, interface_data in graph.find_nodes(name=interface): - graph.add_edge(node_id, interface_id, "implements") - connections += 1 - - return connections - -def _connect_impl_relationships(graph, node_id, node_data): - """Connect impl blocks to their targets and traits""" - connections = 0 - - target = node_data.get("target") - if target and _is_meaningful_type(target): - # Connect to target type - for target_id, target_data in graph.find_nodes(name=target): - graph.add_edge(node_id, target_id, "implements_for") - connections += 1 - - # Connect to implemented traits - traits = node_data.get("traits", []) - for trait in traits: - if _is_meaningful_type(trait): - for trait_id, trait_data in graph.find_nodes(name=trait): - graph.add_edge(node_id, trait_id, "implements") - connections += 1 - - return connections def build_bevy_relationships(graph, created_nodes): - """Build Bevy-specific relationships""" + """Build Bevy-specific relationships (ECS patterns).""" connections = 0 - # Find all plugin structs and impls - plugin_structs = {} - plugin_impls = {} + # Track Bevy systems, components, and resources + systems = [] + components = [] + resources = [] + bundles = [] for node_id, node_data in created_nodes.items(): + m = node_data.get("metadata", {}) + if not m: + continue + + lang = node_data.get("lang") + if lang != "rust": + continue + node_type = node_data.get("type") - name = node_data.get("name", "") + name = node_data.get("name") - # Collect plugin structs - if node_type in ["struct", "class"] and name.endswith("Plugin"): - plugin_structs[name] = node_id + # Detect Bevy systems (functions with Query parameters) + if node_type in ["function", "method"]: + params = m.get("parameters", []) + has_query = any("Query" in str(p) for p in params) + has_commands = any("Commands" in str(p) for p in params) + has_res = any("Res" in str(p) or "ResMut" in str(p) for p in params) - # Collect plugin impls - elif node_type == "impl" and node_data.get("target", "").endswith("Plugin"): - plugin_impls[node_data["target"]] = node_id + if has_query or has_commands or has_res: + systems.append(node_id) - # Connect plugin impls to their structs - for plugin_name, impl_id in plugin_impls.items(): - if plugin_name in plugin_structs: - struct_id = plugin_structs[plugin_name] - graph.add_edge(impl_id, struct_id, "implements_plugin") - connections += 1 + # Detect Bevy components (structs with Component derive) + if node_type == "struct": + derives = m.get("derives", []) + if "Component" in str(derives): + components.append(node_id) - # Find build method in this impl - impl_methods = created_nodes[impl_id].get("methods", []) - for method_name in impl_methods: - if method_name == "build": - # Find the method node - method_id = f"{created_nodes[impl_id]['lang']}::method::{created_nodes[impl_id]['file']}::{plugin_name}.{method_name}" - if method_id in created_nodes: - graph.add_edge(impl_id, method_id, "defines_plugin") - connections += 1 + # Detect Bevy resources (structs with Resource derive) + if node_type == "struct": + derives = m.get("derives", []) + if "Resource" in str(derives): + resources.append(node_id) - # Connect systems to plugins (simplified - same file assumption) - for node_id, node_data in created_nodes.items(): - if node_data.get("type") in ["function", "method"]: - # Simple heuristic for Bevy systems - function_name = node_data.get("name", "").lower() - if any(keyword in function_name for keyword in ['system', 'update', 'setup']): - file_path = node_data.get("file") - # Look for plugins in the same file - for plugin_id, plugin_data in created_nodes.items(): - if (plugin_data.get("file") == file_path and - plugin_data.get("type") in ["struct", "class"] and - plugin_data.get("name", "").endswith("Plugin")): - graph.add_edge(plugin_id, node_id, "contains_system") + # Detect Bevy bundles (structs with Bundle derive) + if node_type == "struct": + derives = m.get("derives", []) + if "Bundle" in str(derives): + bundles.append(node_id) + + # Connect systems to the components/resources they use + for system_id in systems: + system_data = created_nodes.get(system_id, {}) + m = system_data.get("metadata", {}) + params = m.get("parameters", []) + + for param in params: + param_str = str(param) + + # Extract component types from Query + query_match = re.findall(r'Query<[^>]*?([A-Z]\w+)', param_str) + for comp_name in query_match: + for comp_id in components: + comp_data = created_nodes.get(comp_id, {}) + if comp_data.get("name") == comp_name: + if not graph.graph.has_edge(system_id, comp_id): + graph.add_edge(system_id, comp_id, "queries_component") + connections += 1 + + # Extract resource types from Res + res_match = re.findall(r'Res(?:Mut)?<([A-Z]\w+)', param_str) + for res_name in res_match: + for res_id in resources: + res_data = created_nodes.get(res_id, {}) + if res_data.get("name") == res_name: + if not graph.graph.has_edge(system_id, res_id): + graph.add_edge(system_id, res_id, "uses_resource") + connections += 1 + + # Connect bundles to their component fields + for bundle_id in bundles: + bundle_data = created_nodes.get(bundle_id, {}) + m = bundle_data.get("metadata", {}) + fields = m.get("fields", []) + + for field in fields: + field_type = _extract_field_type(field) + for comp_id in components: + comp_data = created_nodes.get(comp_id, {}) + if comp_data.get("name") == field_type: + if not graph.graph.has_edge(bundle_id, comp_id): + graph.add_edge(bundle_id, comp_id, "bundles_component") connections += 1 return connections +def _create_import_symbolic_nodes(graph, import_node_id, file_node_id, import_path, lang, created_nodes): + """Create rich symbolic nodes for imports to build dense graph structure.""" + if not import_path: + return + + # Normalize path separators based on language + if lang == "python": + parts = import_path.replace('.', '::').split('::') + elif lang in ["javascript", "typescript"]: + # Handle relative imports + if import_path.startswith('.'): + return # Skip relative imports for symbolic nodes + parts = import_path.replace('/', '::').replace('@', '').split('::') + elif lang == "rust": + parts = import_path.split('::') + elif lang == "go": + parts = import_path.split('/') + elif lang == "java": + parts = import_path.split('.') + else: + parts = import_path.replace('.', '::').replace('/', '::').split('::') + + parts = [p.strip() for p in parts if p.strip()] + if not parts: + return + + # Create package/crate node (first part) + package_name = parts[0] + package_node_id = f"package::{lang}::{package_name}" + + if package_node_id not in graph.graph.nodes: + graph.add_node( + package_node_id, + type=package_name, # Use package name as type for rich node types + name=package_name, + file=import_path, + lang=lang, + is_external=True + ) + created_nodes[package_node_id] = { + "type": package_name, + "name": package_name, + "file": import_path, + "lang": lang + } + + # Connect file to package + if not graph.graph.has_edge(file_node_id, package_node_id): + graph.add_edge(file_node_id, package_node_id, "imports") + + # Create intermediate module/namespace nodes + current_path = package_name + parent_node = package_node_id + + for part in parts[1:]: + current_path = f"{current_path}::{part}" + module_node_id = f"symbol::{lang}::{current_path}" + + if module_node_id not in graph.graph.nodes: + graph.add_node( + module_node_id, + type=part, # Use part name as type for rich node types + name=current_path, + file=import_path, + lang=lang, + is_symbol=True + ) + created_nodes[module_node_id] = { + "type": part, + "name": current_path, + "file": import_path, + "lang": lang + } + + # Connect parent to child + if not graph.graph.has_edge(parent_node, module_node_id): + graph.add_edge(parent_node, module_node_id, "contains") + + parent_node = module_node_id + # Connect the import node to the final symbol + if parent_node != package_node_id and not graph.graph.has_edge(import_node_id, parent_node): + graph.add_edge(import_node_id, parent_node, "imports") + def _extract_parameter_type(param): """Extract type from parameter string, handling multiple formats""" if not param: