From 269206a7f26b0766a3540662867425f498eb7916 Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Fri, 14 Nov 2025 00:35:41 +0000 Subject: [PATCH] Update mcp_codebase.py --- mcp_codebase.py | 275 ++++++++++++++++++++++++------------------------ 1 file changed, 138 insertions(+), 137 deletions(-) diff --git a/mcp_codebase.py b/mcp_codebase.py index 41d5e4e..0604b81 100644 --- a/mcp_codebase.py +++ b/mcp_codebase.py @@ -2096,83 +2096,117 @@ def build_indexes(): block_type = m.get("block_type", "") component_name = m.get("component_name") + # Skip empty/invalid nodes + if not name or node_type == "unknown": + continue + # Ensure file node exists file_node_id = f"file::{fpath}" graph.add_node(file_node_id, type="File", path=fpath, lang=lang) + # Create node ID with language + node_id = f"{lang}::{node_type}::{fpath}::{name}" + + # Base attributes for all nodes + base_attrs = { + "name": name, + "file": fpath, + "line": m.get("line"), + "lang": lang # Always include language + } + # Create appropriate node based on type if node_type in ["function", "method", "constructor"]: # Function-like entities if node_type == "method" and "class" in m: - # Method belongs to a class + # Method belongs to a class - use compound ID node_id = f"{lang}::{node_type}::{fpath}::{m['class']}.{name}" - graph.add_node(node_id, type=node_type, name=name, file=fpath, - class_name=m.get("class"), line=m.get("line"), - return_type=m.get("return_type"), parameters=m.get("parameters", [])) + graph.add_node(node_id, **base_attrs, type=node_type, + class_name=m.get("class"), + return_type=m.get("return_type"), + parameters=m.get("parameters", [])) + # Connect method to its class class_node_id = f"{lang}::class::{fpath}::{m['class']}" graph.add_edge(class_node_id, node_id, "contains") + # Connect return type if available return_type = m.get("return_type") if return_type and return_type != "void": - # Try to find return type class/interface for type_id, type_data in graph.find_nodes(name=return_type): graph.add_edge(node_id, type_id, "returns") else: # Regular function - node_id = f"{lang}::{node_type}::{fpath}::{name}" - graph.add_node(node_id, type=node_type, name=name, file=fpath, line=m.get("line"), - return_type=m.get("return_type"), parameters=m.get("parameters", [])) + graph.add_node(node_id, **base_attrs, type=node_type, + return_type=m.get("return_type"), + parameters=m.get("parameters", [])) graph.add_edge(file_node_id, node_id, "contains") elif node_type in ["class", "struct", "interface", "enum"]: # Type definitions - node_id = f"{lang}::{node_type}::{fpath}::{name}" - graph.add_node(node_id, type=node_type, name=name, file=fpath, line=m.get("line"), - extends=m.get("extends"), implements=m.get("implements", [])) + graph.add_node(node_id, **base_attrs, type=node_type, + extends=m.get("extends"), + implements=m.get("implements", []), + fields=m.get("fields", []), # Store fields as metadata, not separate nodes + variants=m.get("variants", [])) # For enums + graph.add_edge(file_node_id, node_id, "contains") + # Inheritance relationships extends_class = m.get("extends") if extends_class: - # Try to find the parent class node - for parent_id, parent_data in graph.find_nodes(type="class", name=extends_class): + for parent_id, parent_data in graph.find_nodes(name=extends_class): graph.add_edge(node_id, parent_id, "extends") + # Interface implementation implements_interfaces = m.get("implements", []) for interface_name in implements_interfaces: - for interface_id, interface_data in graph.find_nodes(type="interface", name=interface_name): + for interface_id, interface_data in graph.find_nodes(name=interface_name): graph.add_edge(node_id, interface_id, "implements") - elif node_type == "field": - # Class/struct fields - node_id = f"{lang}::field::{fpath}::{name}" - graph.add_node(node_id, type=node_type, name=name, file=fpath, line=m.get("line"), - field_type=m.get("field_type")) - # If we have class context, connect to class - if "class" in m: - class_node_id = f"{lang}::class::{fpath}::{m['class']}" - graph.add_edge(class_node_id, node_id, "contains") - else: - graph.add_edge(file_node_id, node_id, "contains") + # Bevy-specific: Plugin implementation detection + if node_type == "struct" and name.endswith("Plugin"): + graph.add_node(node_id, **base_attrs, type=node_type, is_bevy_plugin=True) - field_type = m.get("field_type") - if field_type and field_type != "unknown": - # Try to find the field type class - for type_id, type_data in graph.find_nodes(name=field_type): - graph.add_edge(node_id, type_id, "type_reference") + elif node_type == "impl": + # Implementation blocks + target = m.get("target", "unknown") + node_id = f"{lang}::{node_type}::{fpath}::{target}" - elif node_type in ["style", "markup", "code_block"]: - # Code blocks and stylistic elements - block_type = m.get("block_type", node_type) - node_id = f"{lang}::{block_type}::{fpath}::{name}" - graph.add_node(node_id, type=node_type, name=name, file=fpath, - line=m.get("line"), block_type=block_type) + graph.add_node(node_id, **base_attrs, type=node_type, + target=target, + traits=m.get("traits", []), + methods=m.get("methods", [])) + + graph.add_edge(file_node_id, node_id, "contains") + + # Connect to target type + for target_id, target_data in graph.find_nodes(name=target): + graph.add_edge(node_id, target_id, "implements_for") + + # Connect to implemented traits + for trait_name in m.get("traits", []): + for trait_id, trait_data in graph.find_nodes(name=trait_name): + graph.add_edge(node_id, trait_id, "implements") + + # Bevy-specific: Plugin detection + if target.endswith("Plugin"): + graph.add_node(node_id, **base_attrs, type=node_type, is_bevy_plugin_impl=True) + + elif node_type == "trait": + # Traits + graph.add_node(node_id, **base_attrs, type=node_type, + methods=m.get("methods", [])) + graph.add_edge(file_node_id, node_id, "contains") + + elif node_type == "module": + # Modules + graph.add_node(node_id, **base_attrs, type=node_type) graph.add_edge(file_node_id, node_id, "contains") else: # Generic code element fallback - node_id = f"{lang}::{node_type}::{fpath}::{name}" - graph.add_node(node_id, type=node_type, name=name, file=fpath, line=m.get("line")) + graph.add_node(node_id, **base_attrs, type=node_type) graph.add_edge(file_node_id, node_id, "contains") # Handle imports if present @@ -2183,115 +2217,82 @@ def build_indexes(): for call in m.get("calls", []): graph.add_edge(node_id, call, "calls") - # Go-specific relationships - if lang == "go": - if node_type == "method": - # Connect method to its receiver type - receiver_type = m.get("receiver_base_type") - if receiver_type: - # Try to find the receiver type (struct/interface) - for type_id, type_data in graph.find_nodes(type="struct", name=receiver_type): - graph.add_edge(type_id, node_id, "has_method") - for type_id, type_data in graph.find_nodes(type="interface", name=receiver_type): - graph.add_edge(type_id, node_id, "has_method") - - elif node_type == "struct": - # Connect struct to its fields (if we had field type info) - fields = m.get("fields", []) - # Could potentially connect fields to their types if we extract field types - - elif node_type == "interface": - # Connect interface to its required methods - interface_methods = m.get("interface_methods", []) - # This helps identify what types implement this interface - - elif lang == "svelte" and component_name: - # Create component node if it doesn't exist - component_node_id = f"svelte::component::{fpath}::{component_name}" - if component_node_id not in graph.graph: - graph.add_node(component_node_id, type="component", name=component_name, - file=fpath, language="svelte") - graph.add_edge(file_node_id, component_node_id, "contains") - - # Connect different parts to the component - if node_type in ["script", "script_module", "style", "markup"]: - graph.add_edge(component_node_id, node_id, "contains") - - # Component props and interface - if node_type == "prop": - graph.add_edge(component_node_id, node_id, "has_prop") - - # Component usage relationships - if node_type == "markup": - used_components = m.get("used_components", []) - for used_comp in used_components: - # Try to find the used component in the graph - for comp_id, comp_data in graph.find_nodes(type="component", name=used_comp): - graph.add_edge(component_node_id, comp_id, "uses_component") - - # Event handler relationships - event_handlers = m.get("event_handlers", []) - for event in event_handlers: - graph.add_edge(component_node_id, f"event::{event}", "handles_event") - - # Svelte script relationships (TypeScript) - elif lang == "typescript" and block_type in ["script", "script_module"]: - # Connect TypeScript declarations to their Svelte component - if component_name: - component_node_id = f"svelte::component::{fpath}::{component_name}" - graph.add_edge(component_node_id, node_id, "contains") - - # Import relationships - for imp in m.get("imports", []): - # Try to resolve imports to other components/files - if imp.endswith('.svelte'): - imported_component = Path(imp).stem - if imported_component[0].islower(): - imported_component = imported_component[0].upper() + imported_component[1:] - # Look for the imported component - for comp_id, comp_data in graph.find_nodes(type="component", name=imported_component): - graph.add_edge(node_id, comp_id, "imports") - elif lang == "rust": + # Rust-specific relationships + if lang == "rust": if node_type == "struct": - # Connect struct to its fields + # Connect struct fields as metadata, not separate nodes fields = m.get("fields", []) for field in fields: - graph.add_edge(node_id, f"rust::field::{fpath}::{name}.{field}", "has_field") + # Store field info as node attribute instead of creating separate nodes + if "field_types" not in graph.nodes[node_id]: + graph.nodes[node_id]["field_types"] = [] + graph.nodes[node_id]["field_types"].append(field) elif node_type == "enum": - # Connect enum to its variants + # Connect enum variants as metadata variants = m.get("variants", []) for variant in variants: - graph.add_edge(node_id, f"rust::variant::{fpath}::{name}::{variant}", "has_variant") - - elif node_type == "trait": - # Connect trait to its required methods - methods = m.get("methods", []) - for method in methods: - graph.add_edge(node_id, f"rust::trait_method::{name}::{method}", "requires_method") - - elif node_type == "impl": - # Connect impl block to its target type - target = m.get("target") - if target: - # Try to find the target struct/enum - for target_id, target_data in graph.find_nodes(name=target): - graph.add_edge(node_id, target_id, "implements_for") - - # Connect impl block to implemented traits - traits = m.get("traits", []) - for trait_name in traits: - for trait_id, trait_data in graph.find_nodes(type="trait", name=trait_name): - graph.add_edge(node_id, trait_id, "implements") + if "variants" not in graph.nodes[node_id]: + graph.nodes[node_id]["variants"] = [] + graph.nodes[node_id]["variants"].append(variant) elif node_type == "function": - # Connect function to its return type - return_type = m.get("return_type") - if return_type: - # Try to find return type in graph - for type_id, type_data in graph.find_nodes(name=return_type): - graph.add_edge(node_id, type_id, "returns") + # Connect function parameters to their types + parameters = m.get("parameters", []) + for param in parameters: + # Extract type from "Type param_name" format + param_parts = param.split() + if len(param_parts) >= 2: + param_type = param_parts[0] + if param_type not in ['self', '&self', '&mut self', 'mut']: + for type_id, type_data in graph.find_nodes(name=param_type): + graph.add_edge(node_id, type_id, "uses_parameter") + # Bevy-specific relationship detection + if lang == "rust" and node_type == "function": + # Detect Bevy system functions + function_name = name.lower() + if any(keyword in function_name for keyword in ['system', 'plugin', 'build', 'update']): + graph.nodes[node_id]["is_bevy_system"] = True + + # Detect query parameters for system dependencies + parameters = m.get("parameters", []) + for param in parameters: + if 'Query' in param: + graph.nodes[node_id]["has_bevy_query"] = True + if 'Res<' in param or 'ResMut<' in param: + graph.nodes[node_id]["has_bevy_resource"] = True + if 'Commands' in param: + graph.nodes[node_id]["has_bevy_commands"] = True + + # Second pass for Bevy-specific relationships + bevy_connections = 0 + + for node_id, node_data in graph.graph.nodes(data=True): + if node_data.get('lang') == 'rust': + # Connect Plugin impls to their build functions + if node_data.get('type') == 'impl' and node_data.get('is_bevy_plugin_impl'): + plugin_struct = node_data.get('target') + if plugin_struct: + # Find the build method in this impl + for method_id, method_data in graph.find_nodes(type="method", class_name=plugin_struct): + if method_data.get('name') == 'build': + graph.add_edge(node_id, method_id, "defines_plugin") + bevy_connections += 1 + + # Connect systems to their plugins + elif node_data.get('is_bevy_system'): + # Try to find which plugin this system belongs to + file_path = node_data.get('file', '') + if file_path: + # Look for plugins in the same file + for plugin_id, plugin_data in graph.find_nodes(type="struct", file=file_path): + if plugin_data.get('name', '').endswith('Plugin'): + graph.add_edge(plugin_id, node_id, "contains_system") + bevy_connections += 1 + + if bevy_connections > 0: + logger.info(f"Added {bevy_connections} Bevy-specific relationships") logger.info("Building method parameter type relationships...") param_connections = 0 for node_id, node_data in graph.graph.nodes(data=True):