graph build fix again

This commit is contained in:
2025-11-14 01:15:15 +00:00
parent 3b5c82cfdc
commit 8c67a6c816
+253 -124
View File
@@ -2085,6 +2085,9 @@ def build_indexes():
graph = LocalGraph(root_path=str(CODEBASE_PATH))
graph.clear()
# Track all created nodes for second-pass relationships
created_nodes = {}
# Create graph nodes/edges from metadata with enhanced relationships
for m in metadatas:
fpath = m.get("file")
@@ -2095,12 +2098,14 @@ def build_indexes():
component_name = m.get("component_name")
# Skip empty/invalid nodes
if not name or node_type == "unknown":
if not name or node_type == "unknown" or not fpath:
continue
# Ensure file node exists
file_node_id = f"file::{fpath}"
if file_node_id not in graph.graph.nodes:
graph.add_node(file_node_id, type="File", path=fpath, lang=lang)
created_nodes[file_node_id] = {"type": "File", "path": fpath}
# Create node ID with language
node_id = f"{lang}::{node_type}::{fpath}::{name}"
@@ -2113,19 +2118,30 @@ def build_indexes():
"lang": lang # Always include language
}
# Store for second-pass processing
created_nodes[node_id] = base_attrs.copy()
created_nodes[node_id]["type"] = node_type
# 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 - use compound ID
node_id = f"{lang}::{node_type}::{fpath}::{m['class']}.{name}"
class_name = m['class']
node_id = f"{lang}::{node_type}::{fpath}::{class_name}.{name}"
# Update created_nodes with new ID
created_nodes[node_id] = base_attrs.copy()
created_nodes[node_id]["type"] = node_type
created_nodes[node_id]["class_name"] = class_name
graph.add_node(node_id, **base_attrs, type=node_type,
class_name=m.get("class"),
class_name=class_name,
return_type=m.get("return_type"),
parameters=m.get("parameters", []))
# Connect method to its class
class_node_id = f"{lang}::class::{fpath}::{m['class']}"
class_node_id = f"{lang}::class::{fpath}::{class_name}"
graph.add_edge(class_node_id, node_id, "contains")
# Connect return type if available
@@ -2153,6 +2169,7 @@ def build_indexes():
graph.add_node(node_id, **base_attrs, type=node_type,
return_type=m.get("return_type"),
parameters=m.get("parameters", []))
# Connect function to file
graph.add_edge(file_node_id, node_id, "contains")
# Connect return type if available
@@ -2181,27 +2198,17 @@ def build_indexes():
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
fields=m.get("fields", []),
variants=m.get("variants", []))
# Connect type to file
graph.add_edge(file_node_id, node_id, "contains")
# Inheritance relationships
extends_class = m.get("extends")
if 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(name=interface_name):
graph.add_edge(node_id, interface_id, "implements")
# Bevy-specific: Plugin implementation detection
if node_type == "struct" and name.endswith("Plugin"):
# FIX: Use add_node with attributes instead of modifying existing node
graph.add_node(node_id, **base_attrs, type=node_type, is_bevy_plugin=True)
# Store additional attributes for second-pass
created_nodes[node_id]["extends"] = m.get("extends")
created_nodes[node_id]["implements"] = m.get("implements", [])
created_nodes[node_id]["fields"] = m.get("fields", [])
created_nodes[node_id]["variants"] = m.get("variants", [])
elif node_type == "impl":
# Implementation blocks
@@ -2213,21 +2220,15 @@ def build_indexes():
traits=m.get("traits", []),
methods=m.get("methods", []))
# Connect impl to file
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"):
# FIX: Use add_node with attributes instead of modifying existing node
graph.add_node(node_id, **base_attrs, type=node_type, is_bevy_plugin_impl=True)
# Store for second-pass
created_nodes[node_id] = base_attrs.copy()
created_nodes[node_id]["type"] = node_type
created_nodes[node_id]["target"] = target
created_nodes[node_id]["traits"] = m.get("traits", [])
created_nodes[node_id]["methods"] = m.get("methods", [])
elif node_type == "trait":
# Traits
@@ -2235,6 +2236,8 @@ def build_indexes():
methods=m.get("methods", []))
graph.add_edge(file_node_id, node_id, "contains")
created_nodes[node_id]["methods"] = m.get("methods", [])
elif node_type == "module":
# Modules
graph.add_node(node_id, **base_attrs, type=node_type)
@@ -2247,105 +2250,31 @@ def build_indexes():
# Handle imports if present
for imp in m.get("imports", []):
# Create import node if it doesn't exist
if imp not in graph.graph.nodes:
graph.add_node(imp, type="Import", name=imp)
graph.add_edge(file_node_id, imp, "imports")
# Handle function calls if present
for call in m.get("calls", []):
# Create call target node if it doesn't exist
if call not in graph.graph.nodes:
graph.add_node(call, type="FunctionCall", name=call)
graph.add_edge(node_id, call, "calls")
# Rust-specific relationships - FIX: Use graph.graph.nodes
if lang == "rust":
if node_type == "struct":
# Connect struct fields as metadata, not separate nodes
fields = m.get("fields", [])
for field in fields:
# Store field info as node attribute instead of creating separate nodes
if "field_types" not in graph.graph.nodes[node_id]:
graph.graph.nodes[node_id]["field_types"] = []
graph.graph.nodes[node_id]["field_types"].append(field)
# -------------------------------------------------
# Step 2: Second-pass relationship building
# -------------------------------------------------
logger.info("Building second-pass relationships...")
elif node_type == "enum":
# Connect enum variants as metadata
variants = m.get("variants", [])
for variant in variants:
if "variants" not in graph.graph.nodes[node_id]:
graph.graph.nodes[node_id]["variants"] = []
graph.graph.nodes[node_id]["variants"].append(variant)
# Build type relationships
type_connections = build_type_relationships(graph, created_nodes)
logger.info(f"Added {type_connections} type relationships")
elif node_type == "function":
# 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 - FIX: Use graph.graph.nodes
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.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.graph.nodes[node_id]["has_bevy_query"] = True
if 'Res<' in param or 'ResMut<' in param:
graph.graph.nodes[node_id]["has_bevy_resource"] = True
if 'Commands' in param:
graph.graph.nodes[node_id]["has_bevy_commands"] = True
# Second pass for Bevy-specific relationships - FIX: Use graph.graph.nodes
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:
# Build Bevy-specific relationships
bevy_connections = build_bevy_relationships(graph, created_nodes)
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):
if node_data.get('type') in ['method', 'function', 'constructor']:
parameters = node_data.get('parameters', [])
for param in parameters:
# Extract type from parameter string "TypeName paramName"
param_type = param.split()[0] if param and ' ' in param else None
if param_type and param_type not in ['void', 'int', 'String', 'boolean', 'long', 'double', 'float']:
# Try to find this type in the graph
for type_id, type_data in graph.find_nodes(name=param_type):
graph.add_edge(node_id, type_id, "uses_parameter")
param_connections += 1
logger.info(f"Added {param_connections} parameter type relationships")
graph.save()
graph.to_json()
graph.to_toon()
@@ -2419,6 +2348,206 @@ def build_indexes():
elapsed = index_build_time - start_time
logger.info(f"Index build complete with contextual weighting applied. Total time: {elapsed:.1f}s")
def build_type_relationships(graph, created_nodes):
"""Build type relationships between nodes (parameter types, return types, etc.)"""
connections = 0
for node_id, node_data in created_nodes.items():
node_type = node_data.get("type")
# Handle function/method parameter and return types
if node_type in ["function", "method", "constructor"]:
connections += _connect_function_types(graph, node_id, node_data)
# Handle class/struct inheritance and implementation
elif node_type in ["class", "struct", "interface"]:
connections += _connect_class_relationships(graph, node_id, node_data)
# Handle impl blocks
elif node_type == "impl":
connections += _connect_impl_relationships(graph, node_id, node_data)
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"""
connections = 0
# Find all plugin structs and impls
plugin_structs = {}
plugin_impls = {}
for node_id, node_data in created_nodes.items():
node_type = node_data.get("type")
name = node_data.get("name", "")
# Collect plugin structs
if node_type in ["struct", "class"] and name.endswith("Plugin"):
plugin_structs[name] = node_id
# Collect plugin impls
elif node_type == "impl" and node_data.get("target", "").endswith("Plugin"):
plugin_impls[node_data["target"]] = 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
# 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
# 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")
connections += 1
return connections
def _extract_parameter_type(param):
"""Extract type from parameter string, handling multiple formats"""
if not param:
return None
# Handle "type: name" format (TypeScript/Python)
if ':' in param:
parts = param.split(':')
if len(parts) >= 2:
return parts[1].strip()
# Handle "Type name" format (Rust/Java/C++)
if ' ' in param:
parts = param.split()
if len(parts) >= 2:
return parts[0].strip()
# Handle complex types with generics
if '<' in param and '>' in param:
# Extract the base type before generics
base_type = param.split('<')[0].strip()
return base_type
return param.strip()
def _clean_type_name(type_name):
"""Clean type names by removing common modifiers"""
if not type_name:
return ""
# Remove common modifiers
modifiers = ['&', 'mut ', 'const ', 'static ', 'pub ', 'private ', 'protected ']
cleaned = type_name
for mod in modifiers:
cleaned = cleaned.replace(mod, '')
# Remove trailing references/pointers
cleaned = cleaned.rstrip('*&')
return cleaned.strip()
def _is_meaningful_type(type_name):
"""Check if a type name is meaningful (not primitive, void, etc.)"""
if not type_name:
return False
meaningless_types = [
'void', 'int', 'i32', 'i64', 'f32', 'f64', 'bool', 'str', 'String',
'char', 'u8', 'u16', 'u32', 'u64', 'usize', 'isize',
'self', '&self', '&mut self', 'mut self', 'Self',
'Result', 'Option', 'Vec', 'String'
]
cleaned = _clean_type_name(type_name)
return (cleaned and
cleaned not in meaningless_types and
not cleaned.startswith('&') and
len(cleaned) > 1)
# -----------------------------
# Build / load indexes with contextual weighting and batch embedding
# -----------------------------