Repository: stakwork/stakgraph
Pull request #78: Graph Testing
Author: login: fayekelmith
Created: 2025-04-15T03:47:03Z | Merged: 2025-04-23T20:16:24Z

- [X] Setup testing



Diff:
diff --git a/ast/src/builder.rs b/ast/src/builder.rs
index 80ac34533..79107545d 100644
--- a/ast/src/builder.rs
+++ b/ast/src/builder.rs
@@ -1,7 +1,8 @@
 use super::repo::{check_revs_files, Repo};
 use crate::lang::graphs::Graph;
 use crate::lang::{asg::NodeData, graphs::NodeType};
-use crate::lang::{ArrayGraph, BTreeMapGraph};
+use crate::lang::{ArrayGraph, BTreeMapGraph, Node};
+use crate::utils::create_node_key;
 use anyhow::{Ok, Result};
 use git_url_parse::GitUrl;
 use lsp::{git::get_commit_hash, strip_root, Cmd as LspCmd, DidOpen};
@@ -503,6 +504,7 @@ pub fn combine_imports(nodes: Vec<NodeData>) -> Vec<NodeData> {
     if nodes.is_empty() {
         return Vec::new();
     }
+    let import_name = create_node_key(Node::new(NodeType::Import, nodes[0].clone()));
     let mut combined_body = String::new();
     let mut current_position = nodes[0].start;
     for (i, node) in nodes.iter().enumerate() {
@@ -529,7 +531,7 @@ pub fn combine_imports(nodes: Vec<NodeData>) -> Vec<NodeData> {
     };
 
     vec![NodeData {
-        name: "import".to_string(),
+        name: import_name,
         file,
         body: combined_body,
         start: nodes[0].start,
diff --git a/ast/src/lang/asg.rs b/ast/src/lang/asg.rs
index c9fd74ae0..e20585498 100644
--- a/ast/src/lang/asg.rs
+++ b/ast/src/lang/asg.rs
@@ -11,7 +11,7 @@ pub struct UniqueKey {
     pub parent: Option<String>,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq)]
+#[derive(Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq, PartialOrd, Ord)]
 pub struct NodeKeys {
     pub name: String,
     pub file: String,
@@ -41,7 +41,7 @@ impl From<&NodeData> for NodeKeys {
     }
 }
 
-#[derive(Clone, Debug, Deserialize, Default)]
+#[derive(Clone, Debug, Deserialize, Default, Eq, PartialEq, PartialOrd, Ord)]
 pub struct NodeData {
     pub name: String,
     pub file: String,
diff --git a/ast/src/lang/graphs/array_graph.rs b/ast/src/lang/graphs/array_graph.rs
index aadb202ea..9aa9bef15 100644
--- a/ast/src/lang/graphs/array_graph.rs
+++ b/ast/src/lang/graphs/array_graph.rs
@@ -1,6 +1,7 @@
 use super::{graph::Graph, *};
 use crate::lang::linker::normalize_backend_path;
 use crate::lang::{Function, FunctionCall, Lang};
+use crate::utils::create_node_key;
 use anyhow::Result;
 use serde::{Deserialize, Serialize};
 use std::collections::BTreeMap;
@@ -27,7 +28,22 @@ impl Graph for ArrayGraph {
     {
         Self::default()
     }
+    fn analysis(&self) {
+        for edge in &self.edges {
+            println!(
+                "From {:?}-{:?} to {:?}-{:?} type: {:?}",
+                edge.source.node_data.name,
+                edge.source.node_type,
+                edge.target.node_data.name,
+                edge.target.node_type,
+                edge.edge
+            );
+        }
 
+        for node in &self.nodes {
+            println!("Node: {:?}-{:?}", node.node_data.name, node.node_type);
+        }
+    }
     fn create_filtered_graph(&self, final_filter: &[String]) -> Self {
         let mut new_graph = Self::new();
 
@@ -62,7 +78,56 @@ impl Graph for ArrayGraph {
         ((self.nodes.len() as u32), (self.edges.len() as u32))
     }
     fn add_edge(&mut self, edge: Edge) {
-        self.edges.push(edge);
+        let is_duplicate = self.edges.iter().any(|existing_edge| {
+            if existing_edge.edge != edge.edge {
+                return false;
+            }
+
+            let source_match = existing_edge.source.node_type == edge.source.node_type
+                && existing_edge.source.node_data.name == edge.source.node_data.name
+                && existing_edge.source.node_data.file == edge.source.node_data.file
+                && existing_edge.source.node_data.verb == edge.source.node_data.verb;
+
+            let target_match = existing_edge.target.node_type == edge.target.node_type
+                && existing_edge.target.node_data.name == edge.target.node_data.name
+                && existing_edge.target.node_data.file == edge.target.node_data.file
+                && existing_edge.target.node_data.verb == edge.target.node_data.verb;
+
+            if source_match && target_match {
+                match &existing_edge.edge {
+                    EdgeType::Calls(meta) => {
+                        if let Some(operand1) = &meta.operand {
+                            if let EdgeType::Calls(meta2) = &edge.edge {
+                                if let Some(operand2) = &meta2.operand {
+                                    return operand1 == operand2;
+                                }
+                            }
+                        }
+
+                        return true;
+                    }
+                    _ => true,
+                }
+            } else {
+                false
+            }
+        });
+        if !is_duplicate {
+            self.edges.push(edge);
+        }
+    }
+
+    fn add_node(&mut self, node_type: NodeType, node_data: NodeData) {
+        let new_node = Node::new(node_type, node_data.clone());
+        let key = create_node_key(new_node.clone());
+
+        let is_duplicate = self.nodes.iter().any(|node| {
+            let existing_key = create_node_key(node.clone());
+            existing_key == key
+        });
+        if !is_duplicate {
+            self.nodes.push(new_node);
+        }
     }
 
     fn find_nodes_by_name(&self, node_type: NodeType, name: &str) -> Vec<NodeData> {
@@ -113,10 +178,10 @@ impl Graph for ArrayGraph {
             .map(|n| n.node_data.clone())
         {
             let edge = Edge::contains(parent_type, &parent, node_type.clone(), &node_data);
-            self.nodes.push(Node::new(node_type, node_data));
+            self.add_node(node_type, node_data);
             self.add_edge(edge);
         } else {
-            self.nodes.push(Node::new(node_type, node_data));
+            self.add_node(node_type, node_data);
         };
     }
     // NOTE does this need to be per lang on the trait?
@@ -163,6 +228,7 @@ impl Graph for ArrayGraph {
         Ok(())
     }
     fn class_inherits(&mut self) {
+        let mut edges_to_add: Vec<Edge> = Vec::new();
         for n in self.nodes.iter() {
             if n.node_type == NodeType::Class {
                 if let Some(parent) = n.node_data.meta.get("parent") {
@@ -170,13 +236,17 @@ impl Graph for ArrayGraph {
                         self.find_nodes_by_name(NodeType::Class, parent).first()
                     {
                         let edge = Edge::parent_of(&parent_node, &n.node_data);
-                        self.edges.push(edge);
+                        edges_to_add.push(edge);
                     }
                 }
             }
         }
+        for edge in edges_to_add {
+            self.add_edge(edge);
+        }
     }
     fn class_includes(&mut self) {
+        let mut edges_to_add = Vec::new();
         for n in self.nodes.iter() {
             if n.node_type == NodeType::Class {
                 if let Some(includes) = n.node_data.meta.get("includes") {
@@ -184,12 +254,16 @@ impl Graph for ArrayGraph {
                     for m in modules {
                         if let Some(m_node) = self.find_nodes_by_name(NodeType::Class, m).first() {
                             let edge = Edge::class_imports(&n.node_data, &m_node);
-                            self.edges.push(edge);
+                            edges_to_add.push(edge);
                         }
                     }
                 }
             }
         }
+
+        for edge in edges_to_add {
+            self.add_edge(edge);
+        }
     }
 
     fn add_instances(&mut self, instances: Vec<NodeData>) {
@@ -203,7 +277,7 @@ impl Graph for ArrayGraph {
                         &inst.file,
                     );
                     let of_edge = Edge::of(&inst, &cl);
-                    self.edges.push(of_edge);
+                    self.add_edge(of_edge);
                 }
             }
         }
@@ -216,7 +290,7 @@ impl Graph for ArrayGraph {
                 let edge = Edge::contains(NodeType::File, &ff, NodeType::Function, &node);
                 self.add_edge(edge);
             }
-            self.nodes.push(Node::new(NodeType::Function, node.clone()));
+            self.add_node(NodeType::Function, node.clone());
             if let Some(p) = method_of {
                 self.add_edge(p.into());
             }
@@ -227,35 +301,31 @@ impl Graph for ArrayGraph {
                 self.add_edge(rt);
             }
             for r in reqs {
-                // FIXME add operand on calls (axios, api, etc)
-                self.edges.push(Edge::calls(
+                let edge = Edge::calls(
                     NodeType::Function,
                     &node,
                     NodeType::Request,
                     &r,
-                    CallsMeta {
-                        call_start: r.start,
-                        call_end: r.end,
-                        operand: None,
-                    },
-                ));
-                self.nodes.push(Node::new(NodeType::Request, r));
+                    CallsMeta::default(),
+                );
+                self.add_edge(edge);
+                self.add_node(NodeType::Request, r);
             }
             for dm in dms {
-                self.edges.push(dm);
+                self.add_edge(dm);
             }
         }
     }
     fn add_page(&mut self, page: (NodeData, Option<Edge>)) {
         let (p, e) = page;
-        self.nodes.push(Node::new(NodeType::Page, p));
+        self.add_node(NodeType::Page, p);
         if let Some(edge) = e {
-            self.edges.push(edge);
+            self.add_edge(edge);
         }
     }
     fn add_pages(&mut self, pages: Vec<(NodeData, Vec<Edge>)>) {
         for (p, e) in pages {
-            self.nodes.push(Node::new(NodeType::Page, p));
+            self.add_node(NodeType::Page, p);
             for edge in e {
                 self.add_edge(edge);
             }
@@ -284,9 +354,9 @@ impl Graph for ArrayGraph {
                 if self.find_endpoint(&e.name, &e.file, verb).is_some() {
                     continue;
                 }
-                self.nodes.push(Node::new(NodeType::Endpoint, e));
+                self.add_node(NodeType::Endpoint, e);
                 if let Some(edge) = h {
-                    self.edges.push(edge);
+                    self.add_edge(edge);
                 }
             } else {
                 debug!("err missing handler on endpoint!");
@@ -302,7 +372,7 @@ impl Graph for ArrayGraph {
         );
 
         if let Some(edge) = test_edge {
-            self.edges.push(edge);
+            self.add_edge(edge);
         }
     }
     // funcs, tests, integration tests
@@ -318,7 +388,7 @@ impl Graph for ArrayGraph {
                 if let None =
                     self.find_node_by_name_in_file(NodeType::Function, &ext_nd.name, &ext_nd.file)
                 {
-                    self.nodes.push(Node::new(NodeType::Function, ext_nd));
+                    self.add_node(NodeType::Function, ext_nd);
                 }
             } else {
                 self.add_edge(fc.into())
@@ -331,7 +401,7 @@ impl Graph for ArrayGraph {
                 if let None =
                     self.find_node_by_name_in_file(NodeType::Function, &ext_nd.name, &ext_nd.file)
                 {
-                    self.nodes.push(Node::new(NodeType::Function, ext_nd));
+                    self.add_node(NodeType::Function, ext_nd);
                 }
             } else {
                 self.add_edge(Edge::new_test_call(tc));
@@ -476,7 +546,7 @@ impl Graph for ArrayGraph {
                     || node_data.name.contains(path);
 
                 let verb_matches = match node_data.meta.get("verb") {
-                    Some(node_verb) => node_verb.to_uppercase() == verb.to_uppercase(),
+                    Some(node_verb) => node_verb.to_uppercase().contains(&verb.to_uppercase()),
                     None => true,
                 };
 
@@ -486,21 +556,20 @@ impl Graph for ArrayGraph {
             .collect()
     }
     fn find_handlers_for_endpoint(&self, endpoint: &NodeData) -> Vec<NodeData> {
-        // double check if the endpoint is in the graph
-        let endpoint_node = self.nodes.iter().find(|n| {
+        let endp_node = self.nodes.iter().find(|n| {
             n.node_type == NodeType::Endpoint
                 && n.node_data.name == endpoint.name
                 && n.node_data.file == endpoint.file
         });
 
-        if let Some(_endpoint_node) = endpoint_node {
+        if let Some(endpoint) = endp_node {
             self.edges
                 .iter()
                 .filter(|edge| {
                     edge.edge == EdgeType::Handler
                         && edge.source.node_type == NodeType::Endpoint
-                        && edge.source.node_data.name == endpoint.name
-                        && edge.source.node_data.file == endpoint.file
+                        && edge.source.node_data.name == endpoint.node_data.name
+                        && edge.source.node_data.file == endpoint.node_data.file
                 })
                 .filter_map(|edge| {
                     let handler_nodes = self.find_nodes_by_name(
diff --git a/ast/src/lang/graphs/btreemap_graph.rs b/ast/src/lang/graphs/btreemap_graph.rs
index fce21abfb..69a847203 100644
--- a/ast/src/lang/graphs/btreemap_graph.rs
+++ b/ast/src/lang/graphs/btreemap_graph.rs
@@ -1,22 +1,22 @@
 use super::{graph::Graph, *};
 use crate::lang::{Function, FunctionCall, Lang};
-use crate::utils::create_node_key;
+use crate::utils::{create_node_key, create_synthetic_key_from_ref};
 use anyhow::Result;
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, BTreeSet};
 
-use serde::{Deserialize, Serialize};
+use serde::Serialize;
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)]
 pub struct BTreeMapGraph {
     pub nodes: BTreeMap<String, Node>,
-    pub edges: BTreeMap<(String, String), EdgeType>,
+    pub edges: BTreeSet<(String, String, EdgeType)>,
 }
 
 impl Graph for BTreeMapGraph {
     fn new() -> Self {
         BTreeMapGraph {
             nodes: BTreeMap::new(),
-            edges: BTreeMap::new(),
+            edges: BTreeSet::new(),
         }
     }
 
@@ -26,6 +26,14 @@ impl Graph for BTreeMapGraph {
     {
         Self::default()
     }
+    fn analysis(&self) {
+        for (src_key, dst_key, edge_type) in &self.edges {
+            println!("Edge: {:?} -> {:?} type: {:?}", src_key, dst_key, edge_type);
+        }
+        for (node_key, node) in &self.nodes {
+            println!("Node: {:?} type: {:?}", node_key, node.node_type);
+        }
+    }
 
     fn extend_graph(&mut self, other: Self) {
         self.nodes.extend(other.nodes);
@@ -36,22 +44,35 @@ impl Graph for BTreeMapGraph {
         (self.nodes.len() as u32, self.edges.len() as u32)
     }
     fn add_edge(&mut self, edge: Edge) {
+        let old_edge = edge.clone();
         if let (Some(source_node), Some(target_node)) = (
-            self.find_node_from_node_ref(edge.source),
-            self.find_node_from_node_ref(edge.target),
+            self.find_node_from_node_ref(old_edge.source),
+            self.find_node_from_node_ref(old_edge.target),
         ) {
             let source_key = create_node_key(source_node);
             let target_key = create_node_key(target_node);
-            self.edges.insert((source_key, target_key), edge.edge);
+            self.edges.insert((source_key, target_key, old_edge.edge));
+            return;
         }
+
+        let new_edge = edge;
+        let source_key = create_synthetic_key_from_ref(new_edge.source, 0);
+        let target_key = create_synthetic_key_from_ref(new_edge.target, 0);
+        self.edges.insert((source_key, target_key, new_edge.edge));
+    }
+    fn add_node(&mut self, node_type: NodeType, node_data: NodeData) {
+        let node = Node::new(node_type.clone(), node_data.clone());
+        let node_key = create_node_key(node.clone());
+        self.nodes.insert(node_key.clone(), node);
     }
 
     fn find_nodes_by_name(&self, node_type: NodeType, name: &str) -> Vec<NodeData> {
-        let prefix = format!("{:?}-{}", node_type, name.to_lowercase());
+        let prefix = format!("{:?}", node_type).to_lowercase();
 
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
+            .filter(|(_, node)| node.node_data.name == name)
             .map(|(_, node)| node.node_data.clone())
             .collect()
     }
@@ -61,18 +82,13 @@ impl Graph for BTreeMapGraph {
         name: &str,
         file: &str,
     ) -> Option<NodeData> {
-        let prefix = format!(
-            "{:?}-{}-{}",
-            node_type,
-            name.to_lowercase(),
-            file.to_lowercase()
-        );
+        let prefix = format!("{:?}", node_type).to_lowercase();
 
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
+            .find(|(_, node)| node.node_data.name == name && node.node_data.file == file)
             .map(|(_, node)| node.node_data.clone())
-            .next()
     }
 
     fn add_node_with_parent(
@@ -82,18 +98,17 @@ impl Graph for BTreeMapGraph {
         parent_type: NodeType,
         parent_file: &str,
     ) {
-        let node = Node::new(node_type.clone(), node_data.clone());
-        let node_key = create_node_key(node.clone());
+        self.add_node(node_type.clone(), node_data.clone());
 
-        let prefix = format!("{:?}-", parent_type);
-        if let Some((parent_key, _)) = self
+        let prefix = format!("{:?}-", parent_type).to_lowercase();
+        if let Some((_parent_key, parent_node)) = self
             .nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
             .find(|(_, n)| n.node_data.file == parent_file)
         {
-            self.edges
-                .insert((parent_key.clone(), node_key), EdgeType::Contains);
+            let edge = Edge::contains(parent_type, &parent_node.node_data, node_type, &node_data);
+            self.add_edge(edge);
         }
     }
 
@@ -107,14 +122,14 @@ impl Graph for BTreeMapGraph {
             }
         }
 
-        for ((src, dst), edge_type) in &self.edges {
+        for (src, dst, edge_type) in &self.edges {
             if let (Some(src_node), Some(dst_node)) = (self.nodes.get(src), self.nodes.get(dst)) {
                 if final_filter.contains(&src_node.node_data.file)
                     || final_filter.contains(&dst_node.node_data.file)
                 {
                     filtered
                         .edges
-                        .insert((src.clone(), dst.clone()), edge_type.clone());
+                        .insert((src.clone(), dst.clone(), edge_type.clone()));
                 }
             }
         }
@@ -123,7 +138,7 @@ impl Graph for BTreeMapGraph {
     }
 
     fn find_nodes_in_range(&self, node_type: NodeType, row: u32, file: &str) -> Option<NodeData> {
-        let prefix = format!("{:?}-", node_type);
+        let prefix = format!("{:?}-", node_type).to_lowercase();
 
         self.nodes
             .range(prefix.clone()..)
@@ -137,7 +152,7 @@ impl Graph for BTreeMapGraph {
     }
 
     fn find_node_at(&self, node_type: NodeType, file: &str, line: u32) -> Option<NodeData> {
-        let prefix = format!("{:?}-", node_type);
+        let prefix = format!("{:?}-", node_type).to_lowercase();
 
         self.nodes
             .range(prefix.clone()..)
@@ -152,16 +167,16 @@ impl Graph for BTreeMapGraph {
         name: &str,
         suffix: &str,
     ) -> Option<NodeData> {
-        let prefix = format!("{:?}-{}", node_type, name.to_lowercase());
+        let prefix = format!("{:?}-", node_type).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
-            .find(|(_, node)| node.node_data.file.ends_with(suffix))
+            .find(|(_, node)| node.node_data.name == name && node.node_data.file.ends_with(suffix))
             .map(|(_, node)| node.node_data.clone())
     }
 
     fn find_nodes_by_file_ends_with(&self, node_type: NodeType, file: &str) -> Vec<NodeData> {
-        let prefix = format!("{:?}-", node_type);
+        let prefix = format!("{:?}-", node_type).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
@@ -175,7 +190,7 @@ impl Graph for BTreeMapGraph {
         target_name: &str,
         target_file: &str,
     ) -> Option<NodeKeys> {
-        for ((src_key, dst_key), edge) in &self.edges {
+        for (src_key, dst_key, edge) in &self.edges {
             if edge == &edge_type {
                 if let (Some(src_node), Some(dst_node)) =
                     (self.nodes.get(src_key), self.nodes.get(dst_key))
@@ -193,14 +208,17 @@ impl Graph for BTreeMapGraph {
     fn add_instances(&mut self, instances: Vec<NodeData>) {
         for inst in instances {
             if let Some(of) = &inst.data_type {
-                if let Some(class_node) = self.find_nodes_by_name(NodeType::Class, of).first() {
-                    let node = Node::new(NodeType::Instance, inst.clone());
-                    let node_key = create_node_key(node.clone());
-                    self.nodes.insert(node_key.clone(), node);
-
-                    let class_key =
-                        format!("{:?}-{}", NodeType::Class, class_node.name.to_lowercase());
-                    self.edges.insert((node_key, class_key), EdgeType::Of);
+                if let Some(class_node_data) = self.find_nodes_by_name(NodeType::Class, of).first()
+                {
+                    self.add_node_with_parent(
+                        NodeType::Instance,
+                        inst.clone(),
+                        NodeType::File,
+                        &inst.file,
+                    );
+
+                    let edge = Edge::of(&inst, class_node_data);
+                    self.add_edge(edge);
                 }
             }
         }
@@ -210,31 +228,44 @@ impl Graph for BTreeMapGraph {
         for (node, method_of, reqs, dms, trait_operand, return_types) in functions {
             let func_node = Node::new(NodeType::Function, node.clone());
             let func_key = create_node_key(func_node.clone());
-            self.nodes.insert(func_key.clone(), func_node);
+            if !self.nodes.contains_key(&func_key) {
+                self.nodes.insert(func_key.clone(), func_node);
+            }
 
-            let file_prefix = format!("{:?}-{}", NodeType::File, node.file.to_lowercase());
-            if let Some((file_key, _)) = self.nodes.range(file_prefix..).next() {
-                self.edges
-                    .insert((file_key.clone(), func_key.clone()), EdgeType::Contains);
+            let file_prefix = format!("{:?}-", NodeType::File).to_lowercase();
+
+            if let Some((_, file_node)) = self
+                .nodes
+                .range(file_prefix..)
+                .find(|(_, n)| n.node_data.file == node.file)
+            {
+                let edge = Edge::contains(
+                    NodeType::File,
+                    &file_node.node_data,
+                    NodeType::Function,
+                    &node,
+                );
+                self.add_edge(edge);
             }
-            // Add method_of edge if present
+
             if let Some(p) = method_of {
                 self.add_edge(p.into());
             }
 
-            // Add trait operand edge if present
             if let Some(to) = trait_operand {
                 self.add_edge(to.into());
             }
 
-            // Add return type edges
             for rt in return_types {
                 self.add_edge(rt);
             }
+
             for r in reqs {
                 let req_node = Node::new(NodeType::Request, r.clone());
-                self.nodes
-                    .insert(create_node_key(req_node.clone()), req_node);
+                let req_key = create_node_key(req_node.clone());
+                if !self.nodes.contains_key(&req_key) {
+                    self.nodes.insert(req_key, req_node);
+                }
 
                 let edge = Edge::calls(
                     NodeType::Function,
@@ -250,7 +281,6 @@ impl Graph for BTreeMapGraph {
                 self.add_edge(edge);
             }
 
-            // Add datamodel edges
             for dm in dms {
                 self.add_edge(dm);
             }
@@ -259,9 +289,7 @@ impl Graph for BTreeMapGraph {
 
     fn add_page(&mut self, page: (NodeData, Option<Edge>)) {
         let (page_data, edge_opt) = page;
-        let page_node = Node::new(NodeType::Page, page_data.clone());
-        let page_key = create_node_key(page_node.clone());
-        self.nodes.insert(page_key.clone(), page_node);
+        self.add_node(NodeType::Page, page_data);
 
         if let Some(edge) = edge_opt {
             self.add_edge(edge);
@@ -270,9 +298,7 @@ impl Graph for BTreeMapGraph {
 
     fn add_pages(&mut self, pages: Vec<(NodeData, Vec<Edge>)>) {
         for (page_data, edges) in pages {
-            let page_node = Node::new(NodeType::Page, page_data.clone());
-            let page_key = create_node_key(page_node.clone());
-            self.nodes.insert(page_key.clone(), page_node);
+            self.add_node(NodeType::Page, page_data);
 
             for edge in edges {
                 self.add_edge(edge);
@@ -281,12 +307,15 @@ impl Graph for BTreeMapGraph {
     }
 
     fn find_endpoint(&self, name: &str, file: &str, verb: &str) -> Option<NodeData> {
-        let prefix = format!("{:?}-{}-{}", NodeType::Endpoint, name, file);
-
+        let prefix = format!("{:?}-", NodeType::Endpoint).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
-            .find(|(_, node)| node.node_data.meta.get("verb") == Some(&verb.to_string()))
+            .find(|(_, node)| {
+                node.node_data.name == name
+                    && node.node_data.file == file
+                    && node.node_data.meta.get("verb") == Some(&verb.to_string())
+            })
             .map(|(_, node)| node.node_data.clone())
     }
 
@@ -303,9 +332,7 @@ impl Graph for BTreeMapGraph {
                     continue;
                 }
 
-                let endpoint_node = Node::new(NodeType::Endpoint, endpoint_data.clone());
-                let endpoint_key = create_node_key(endpoint_node.clone());
-                self.nodes.insert(endpoint_key.clone(), endpoint_node);
+                self.add_node(NodeType::Endpoint, endpoint_data);
 
                 if let Some(edge) = handler_edge {
                     self.add_edge(edge);
@@ -329,36 +356,33 @@ impl Graph for BTreeMapGraph {
 
     fn add_calls(&mut self, calls: (Vec<FunctionCall>, Vec<FunctionCall>, Vec<Edge>)) {
         let (funcs, tests, int_tests) = calls;
-
         for (fc, ext_func) in funcs {
             if let Some(ext_nd) = ext_func {
-                if let Some(source_node) =
-                    self.find_node_from_node_key(NodeType::Function, fc.source)
-                {
-                    let source_key = create_node_key(source_node);
-                    let ext_node = Node::new(NodeType::Function, ext_nd.clone());
-                    let ext_key = create_node_key(ext_node.clone());
-
-                    if !self.nodes.contains_key(&ext_key) {
-                        self.nodes.insert(ext_key.clone(), ext_node);
-                    }
-                    self.edges.insert((source_key, ext_key), EdgeType::Uses);
+                let ext_node = Node::new(NodeType::Function, ext_nd.clone());
+                let ext_key = create_node_key(ext_node.clone());
+                if !self.nodes.contains_key(&ext_key) {
+                    self.nodes.insert(ext_key, ext_node);
                 }
+
+                let edge = Edge::uses(fc.source, &ext_nd);
+                self.add_edge(edge);
             } else {
                 if let (Some(source_node), Some(target_node)) = (
                     self.find_node_from_node_key(NodeType::Function, fc.source.clone()),
                     self.find_node_from_node_key(NodeType::Function, fc.target),
                 ) {
-                    let source_key = create_node_key(source_node);
-                    let target_key = create_node_key(target_node);
-                    self.edges.insert(
-                        (source_key, target_key),
-                        EdgeType::Calls(CallsMeta {
+                    let edge = Edge::calls(
+                        NodeType::Function,
+                        &source_node.node_data,
+                        NodeType::Function,
+                        &target_node.node_data,
+                        CallsMeta {
                             call_start: fc.call_start,
                             call_end: fc.call_end,
                             operand: fc.operand,
-                        }),
+                        },
                     );
+                    self.add_edge(edge);
                 }
             }
         }
@@ -366,18 +390,13 @@ impl Graph for BTreeMapGraph {
         // Add test function calls
         for (tc, ext_func) in tests {
             if let Some(ext_nd) = ext_func {
-                if let Some(source_node) =
-                    self.find_node_from_node_key(NodeType::Function, tc.source)
-                {
-                    let source_key = create_node_key(source_node);
-                    let ext_node = Node::new(NodeType::Function, ext_nd.clone());
-                    let ext_key = create_node_key(ext_node.clone());
-
-                    if !self.nodes.contains_key(&ext_key) {
-                        self.nodes.insert(ext_key.clone(), ext_node);
-                    }
-                    self.edges.insert((source_key, ext_key), EdgeType::Uses);
+                let ext_node = Node::new(NodeType::Function, ext_nd.clone());
+                let ext_key = create_node_key(ext_node.clone());
+                if !self.nodes.contains_key(&ext_key) {
+                    self.nodes.insert(ext_key, ext_node);
                 }
+                let edge = Edge::uses(tc.source, &ext_nd);
+                self.add_edge(edge);
             }
         }
 
@@ -402,7 +421,8 @@ impl Graph for BTreeMapGraph {
                         )?;
 
                         for end in endpoints_in_group {
-                            let prefix = format!("{:?}-{}", NodeType::Endpoint, end.name);
+                            let prefix =
+                                format!("{:?}-{}", NodeType::Endpoint, end.name).to_lowercase();
                             if let Some((key, node)) = self
                                 .nodes
                                 .range(prefix.clone()..)
@@ -418,8 +438,8 @@ impl Graph for BTreeMapGraph {
                                 let edges_to_update: Vec<_> = self
                                     .edges
                                     .iter()
-                                    .filter(|((src, _), _)| src == key)
-                                    .map(|((_, dst), edge)| (dst.clone(), edge.clone()))
+                                    .filter(|(src, _, _)| src == key)
+                                    .map(|(_, dst, edge)| (dst.clone(), edge.clone()))
                                     .collect();
 
                                 updates.push((key.clone(), updated_node, edges_to_update));
@@ -440,8 +460,9 @@ impl Graph for BTreeMapGraph {
 
             // Update edges
             for (dst, edge) in edges {
-                self.edges.remove(&(old_key.clone(), dst.clone()));
-                self.edges.insert((new_key.clone(), dst), edge);
+                self.edges
+                    .remove(&(old_key.clone(), dst.clone(), edge.clone()));
+                self.edges.insert((new_key.clone(), dst, edge));
             }
         }
 
@@ -488,7 +509,7 @@ impl Graph for BTreeMapGraph {
         }
     }
     fn get_data_models_within(&mut self, lang: &Lang) {
-        let prefix = format!("{:?}-", NodeType::DataModel);
+        let prefix = format!("{:?}-", NodeType::DataModel).to_lowercase();
 
         let data_model_nodes: Vec<NodeData> = self
             .nodes
@@ -516,7 +537,7 @@ impl Graph for BTreeMapGraph {
     ) {
         let mut has_children: BTreeMap<String, bool> = BTreeMap::new();
 
-        let parent_prefix = format!("{:?}-", parent_type);
+        let parent_prefix = format!("{:?}-", parent_type).to_lowercase();
         for (_, node) in self
             .nodes
             .range(parent_prefix.clone()..)
@@ -525,7 +546,7 @@ impl Graph for BTreeMapGraph {
             has_children.insert(node.node_data.name.clone(), false);
         }
 
-        let child_prefix = format!("{:?}-", child_type);
+        let child_prefix = format!("{:?}-", child_type).to_lowercase();
         for (_, node) in self
             .nodes
             .range(child_prefix.clone()..)
@@ -550,9 +571,8 @@ impl Graph for BTreeMapGraph {
 
         for key in nodes_to_remove {
             self.nodes.remove(&key);
-            // Remove associated edges
-            self.edges
-                .retain(|(src, dst), _| src != &key && dst != &key);
+            // self.edges
+            //     .retain(|(src, dst, _)| src != &key && dst != &key);
         }
     }
 
@@ -573,7 +593,7 @@ impl Graph for BTreeMapGraph {
     }
 
     fn find_nodes_by_name_contains(&self, node_type: NodeType, name: &str) -> Vec<NodeData> {
-        let prefix = format!("{:?}-{}", node_type, name);
+        let prefix = format!("{:?}-{}", node_type, name).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, n)| k.starts_with(&prefix) && n.node_data.name.contains(name))
@@ -582,7 +602,7 @@ impl Graph for BTreeMapGraph {
     }
 
     fn find_resource_nodes(&self, node_type: NodeType, verb: &str, path: &str) -> Vec<NodeData> {
-        let prefix = format!("{:?}-", node_type);
+        let prefix = format!("{:?}-", node_type).to_lowercase();
 
         self.nodes
             .range(prefix.clone()..)
@@ -611,7 +631,7 @@ impl Graph for BTreeMapGraph {
 
         let mut handlers = Vec::new();
 
-        for ((src, dst), edge_type) in &self.edges {
+        for (src, dst, edge_type) in &self.edges {
             if *edge_type == EdgeType::Handler && src == &endpoint_key {
                 if let Some(node) = self.nodes.get(dst) {
                     handlers.push(node.node_data.clone());
@@ -623,7 +643,7 @@ impl Graph for BTreeMapGraph {
     }
 
     fn check_direct_data_model_usage(&self, function_name: &str, data_model: &str) -> bool {
-        for ((src_key, dst_key), edge_type) in &self.edges {
+        for (src_key, dst_key, edge_type) in &self.edges {
             if *edge_type == EdgeType::Contains {
                 if let (Some(src_node), Some(dst_node)) =
                     (self.nodes.get(src_key), self.nodes.get(dst_key))
@@ -646,10 +666,11 @@ impl Graph for BTreeMapGraph {
             NodeType::Function,
             function.name,
             function.file
-        );
+        )
+        .to_lowercase();
         let mut called_functions = Vec::new();
 
-        for ((src, dst), edge_type) in &self.edges {
+        for (src, dst, edge_type) in &self.edges {
             if let EdgeType::Calls(_) = edge_type {
                 if src.starts_with(&function_prefix) {
                     if let Some(node) = self.nodes.get(dst) {
@@ -663,7 +684,7 @@ impl Graph for BTreeMapGraph {
     }
 
     fn find_nodes_by_type(&self, node_type: NodeType) -> Vec<NodeData> {
-        let prefix = format!("{:?}-", node_type);
+        let prefix = format!("{:?}-", node_type).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
@@ -678,10 +699,10 @@ impl Graph for BTreeMapGraph {
         edge_type: EdgeType,
     ) -> Vec<(NodeData, NodeData)> {
         let mut result = Vec::new();
-        let source_prefix = format!("{:?}-", source_type);
-        let target_prefix = format!("{:?}-", target_type);
+        let source_prefix = format!("{:?}-", source_type).to_lowercase();
+        let target_prefix = format!("{:?}-", target_type).to_lowercase();
 
-        for ((src_key, dst_key), edge) in &self.edges {
+        for (src_key, dst_key, edge) in &self.edges {
             if *edge == edge_type
                 && src_key.starts_with(&source_prefix)
                 && dst_key.starts_with(&target_prefix)
@@ -699,9 +720,9 @@ impl Graph for BTreeMapGraph {
     fn count_edges_of_type(&self, edge_type: EdgeType) -> usize {
         self.edges
             .iter()
-            .filter(|(_, edge)| match (edge, &edge_type) {
+            .filter(|(_, _, edge)| match (edge, &edge_type) {
                 (EdgeType::Calls(_), EdgeType::Calls(_)) => true,
-                _ => **edge == edge_type,
+                _ => *edge == edge_type,
             })
             .count()
     }
@@ -709,31 +730,33 @@ impl Graph for BTreeMapGraph {
 
 impl BTreeMapGraph {
     fn find_node_from_node_ref(&self, node_ref: NodeRef) -> Option<Node> {
-        let prefix = format!(
-            "{:?}-{}-{}",
-            &node_ref.node_type, &node_ref.node_data.name, &node_ref.node_data.file
-        );
+        let prefix = format!("{:?}-", &node_ref.node_type).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
+            .find(|(_, node)| {
+                node.node_data.file == node_ref.node_data.file
+                    && node.node_data.name == node_ref.node_data.name
+            })
             .map(|(_, node)| node.clone())
-            .next()
     }
 
     fn find_node_from_node_key(&self, node_type: NodeType, node_key: NodeKeys) -> Option<Node> {
-        let prefix = format!("{:?}-{}-{}", &node_type, &node_key.name, &node_key.file);
+        let prefix = format!("{:?}-", &node_type).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
+            .find(|(_, node)| {
+                node.node_data.file == node_key.file && node.node_data.name == node_key.name
+            })
             .map(|(_, node)| node.clone())
-            .next()
     }
 }
 impl Default for BTreeMapGraph {
     fn default() -> Self {
         BTreeMapGraph {
             nodes: BTreeMap::new(),
-            edges: BTreeMap::new(),
+            edges: BTreeSet::new(),
         }
     }
 }
diff --git a/ast/src/lang/graphs/graph.rs b/ast/src/lang/graphs/graph.rs
index 99721f4cb..69880eda7 100644
--- a/ast/src/lang/graphs/graph.rs
+++ b/ast/src/lang/graphs/graph.rs
@@ -18,6 +18,7 @@ pub trait Graph: Default + Debug {
     {
         Self::default()
     }
+    fn analysis(&self);
     fn create_filtered_graph(&self, final_filter: &[String]) -> Self
     where
         Self: Sized;
@@ -37,6 +38,7 @@ pub trait Graph: Default + Debug {
         parent_file: &str,
     );
     fn add_edge(&mut self, edge: Edge);
+    fn add_node(&mut self, node_type: NodeType, node_data: NodeData);
 
     fn find_source_edge_by_name_and_file(
         &self,
diff --git a/ast/src/lang/graphs/mod.rs b/ast/src/lang/graphs/mod.rs
index 387182eb0..4122f51c4 100644
--- a/ast/src/lang/graphs/mod.rs
+++ b/ast/src/lang/graphs/mod.rs
@@ -8,7 +8,7 @@ pub use graph::*;
 
 use crate::lang::asg::*;
 use serde::{Deserialize, Serialize};
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
 pub enum NodeType {
     Repository,
     Language,
@@ -37,20 +37,20 @@ pub enum NodeType {
 //     E2e,
 // }
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Node {
     pub node_type: NodeType,
     pub node_data: NodeData,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
 pub struct Edge {
     pub edge: EdgeType,
     pub source: NodeRef,
     pub target: NodeRef,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq)]
+#[derive(Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq, PartialOrd, Ord)]
 pub struct CallsMeta {
     pub call_start: usize,
     pub call_end: usize,
@@ -58,7 +58,7 @@ pub struct CallsMeta {
     pub operand: Option<String>,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
 #[serde(tag = "edge_type", content = "edge_data")]
 #[serde(rename_all = "UPPERCASE")]
 pub enum EdgeType {
@@ -76,7 +76,7 @@ pub enum EdgeType {
     ParentOf, // Class -> Class
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
 pub struct NodeRef {
     pub node_type: NodeType,
     pub node_data: NodeKeys,
diff --git a/ast/src/lang/parse.rs b/ast/src/lang/parse.rs
index d5818b3fa..35ec4a669 100644
--- a/ast/src/lang/parse.rs
+++ b/ast/src/lang/parse.rs
@@ -98,8 +98,9 @@ impl Lang {
                 impy.body = body;
                 impy.start = node.start_position().row;
                 impy.end = node.end_position().row;
+                res.push(impy);
             }
-            res.push(impy);
+
             Ok(())
         })?;
         Ok(res)
diff --git a/ast/src/lang/queries/kotlin.rs b/ast/src/lang/queries/kotlin.rs
index c97505a06..81eb19e2a 100644
--- a/ast/src/lang/queries/kotlin.rs
+++ b/ast/src/lang/queries/kotlin.rs
@@ -27,9 +27,8 @@ impl Stack for Kotlin {
         Some(format!(
             r#"
             (call_expression
-                (simple_identifier) @method_name
-
-                )
+                (simple_identifier) @{LIBRARY_NAME}
+            )@{LIBRARY}
             "#
         ))
     }
diff --git a/ast/src/lang/queries/python.rs b/ast/src/lang/queries/python.rs
index 0e029ebfe..eb576de5a 100644
--- a/ast/src/lang/queries/python.rs
+++ b/ast/src/lang/queries/python.rs
@@ -174,6 +174,44 @@ impl Stack for Python {
         ]
     }
 
+    fn add_endpoint_verb(&self, nd: &mut NodeData, call: &Option<String>) {
+        if nd.meta.get("verb").is_some() {
+            return;
+        }
+
+        if let Some(c) = call {
+            let verb = c.to_uppercase();
+            if !verb.is_empty() {
+                nd.add_verb(&verb);
+                return;
+            }
+        }
+
+        if let Some(handler) = nd.meta.get("handler").cloned() {
+            let method_name = if handler.contains('.') {
+                handler.split('.').last().unwrap_or(&handler)
+            } else {
+                &handler
+            };
+
+            let verb = if method_name.starts_with("get_") || method_name == "index" {
+                "GET"
+            } else if method_name.starts_with("post_") || method_name.starts_with("create_") {
+                "POST"
+            } else if method_name.starts_with("delete_") || method_name.starts_with("remove_") {
+                "DELETE"
+            } else if method_name.starts_with("put_") || method_name.starts_with("update_") {
+                "PUT"
+            } else {
+                // Default to GET if no specific verb can be inferred
+                "GET"
+            };
+
+            nd.add_verb(verb);
+        } else {
+            nd.add_verb("GET");
+        }
+    }
     fn data_model_query(&self) -> Option<String> {
         Some(format!(
             "(class_definition
@@ -208,4 +246,53 @@ impl Stack for Python {
     fn is_test(&self, func_name: &str, _func_file: &str) -> bool {
         func_name.starts_with("test_")
     }
+    fn handler_finder(
+        &self,
+        endpoint: NodeData,
+        find_fn: &dyn Fn(&str, &str) -> Option<NodeData>,
+        find_fns_in: &dyn Fn(&str) -> Vec<NodeData>,
+        _handler_params: HandlerParams,
+    ) -> Vec<(NodeData, Option<Edge>)> {
+        if let Some(handler) = endpoint.meta.get("handler") {
+            if handler.contains('.') {
+                //Django Style
+                let parts: Vec<&str> = handler.split('.').collect();
+                let module_name = parts[0];
+                let function_name = parts[1];
+
+                let dir_path = endpoint.file.rsplitn(2, '/').nth(1).unwrap_or("");
+
+                let possible_module_paths = vec![
+                    format!("{}/{}.py", dir_path, module_name), // standard module
+                    format!("{}/{}/views.py", dir_path, module_name), // app views
+                ];
+
+                for module_path in possible_module_paths {
+                    if let Some(nd) = find_fn(function_name, &module_path) {
+                        let edge = Edge::handler(&endpoint, &nd);
+                        return vec![(endpoint, Some(edge))];
+                    }
+                }
+                let py_files = find_fns_in("py");
+                for func in py_files {
+                    if func.name == function_name {
+                        let edge = Edge::handler(&endpoint, &func);
+                        return vec![(endpoint, Some(edge))];
+                    }
+                }
+            } else {
+                // Flask/FastAPI Style
+                if let Some(nd) = find_fn(handler, &endpoint.file) {
+                    let edge = Edge::handler(&endpoint, &nd);
+                    return vec![(endpoint, Some(edge))];
+                }
+            }
+        }
+
+        vec![(endpoint, None)]
+    }
+
+    fn use_handler_finder(&self) -> bool {
+        true
+    }
 }
diff --git a/ast/src/repo.rs b/ast/src/repo.rs
index 53487baef..f5ca96583 100644
--- a/ast/src/repo.rs
+++ b/ast/src/repo.rs
@@ -33,7 +33,7 @@ pub struct Repos(pub Vec<Repo>);
 
 impl Repos {
     pub async fn build_graphs(&self) -> Result<ArrayGraph> {
-        self.build_graphs_inner::<ArrayGraph>().await
+        self.build_graphs_inner().await
     }
 
     pub async fn build_graphs_btree(&self) -> Result<BTreeMapGraph> {
diff --git a/ast/src/testing/angular/mod.rs b/ast/src/testing/angular/mod.rs
index d45dc9409..f8bbd224a 100644
--- a/ast/src/testing/angular/mod.rs
+++ b/ast/src/testing/angular/mod.rs
@@ -18,7 +18,7 @@ pub async fn test_angular_generic<G: Graph>() -> Result<(), anyhow::Error> {
     let graph = repo.build_graph_inner::<G>().await?;
 
     let (num_nodes, num_edges) = graph.get_graph_size();
-    assert_eq!(num_nodes, 77, "Expected 77 nodes");
+    assert_eq!(num_nodes, 76, "Expected 76 nodes");
     assert_eq!(num_edges, 78, "Expected 78 edges");
 
     let imports = graph.find_nodes_by_type(NodeType::Import);
@@ -46,7 +46,7 @@ pub async fn test_angular_generic<G: Graph>() -> Result<(), anyhow::Error> {
 
     // Test requests
     let requests = graph.find_nodes_by_type(NodeType::Request);
-    assert_eq!(requests.len(), 8, "Expected 8 requests");
+    assert_eq!(requests.len(), 7, "Expected 7 requests");
 
     // Test calls edges
     let calls_edges_count = graph.count_edges_of_type(EdgeType::Calls(Default::default()));
@@ -57,8 +57,9 @@ pub async fn test_angular_generic<G: Graph>() -> Result<(), anyhow::Error> {
 
 #[test(tokio::test)]
 async fn test_angular() {
-    use crate::lang::graphs::ArrayGraph;
+    use crate::lang::graphs::{ArrayGraph, BTreeMapGraph};
     test_angular_generic::<ArrayGraph>().await.unwrap();
+    test_angular_generic::<BTreeMapGraph>().await.unwrap();
 }
 
 // #[test(tokio::test)]
diff --git a/ast/src/testing/go/mod.rs b/ast/src/testing/go/mod.rs
index 19bb0b13f..08d9de8ba 100644
--- a/ast/src/testing/go/mod.rs
+++ b/ast/src/testing/go/mod.rs
@@ -20,7 +20,7 @@ pub async fn test_go_generic<G: Graph>() -> Result<(), anyhow::Error> {
     let (num_nodes, num_edges) = graph.get_graph_size();
     if use_lsp == true {
         assert_eq!(num_nodes, 64, "Expected 64 nodes");
-        assert_eq!(num_edges, 108, "Expected 108 edges");
+        assert_eq!(num_edges, 92, "Expected 92 edges");
     } else {
         assert_eq!(num_nodes, 30, "Expected 30 nodes");
         assert_eq!(num_edges, 48, "Expected 48 edges");
@@ -88,8 +88,9 @@ pub async fn test_go_generic<G: Graph>() -> Result<(), anyhow::Error> {
 
 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
 async fn test_go() {
-    use crate::lang::graphs::ArrayGraph;
+    use crate::lang::graphs::{ArrayGraph, BTreeMapGraph};
     test_go_generic::<ArrayGraph>().await.unwrap();
+    test_go_generic::<BTreeMapGraph>().await.unwrap();
 }
 
 // #[test(tokio::test)]
diff --git a/ast/src/testing/graphs.rs b/ast/src/testing/graphs.rs
new file mode 100644
index 000000000..6eb88f070
--- /dev/null
+++ b/ast/src/testing/graphs.rs
@@ -0,0 +1,175 @@
+use crate::lang::graphs::{BTreeMapGraph, Node, NodeType};
+use crate::lang::{ArrayGraph, Graph, Lang};
+use crate::repo::Repo;
+use anyhow::Result;
+use std::str::FromStr;
+
+type CriticalEdgeCheck = (NodeType, &'static str);
+pub struct GraphTestExpectations {
+    pub lang_id: &'static str,
+    pub repo_path: &'static str,
+    pub expected_nodes: u32,
+    pub expected_edges: u32,
+    pub expected_nodes_lsp: Option<u32>,
+    pub expected_edges_lsp: Option<u32>,
+    pub _critical_edges: Vec<CriticalEdgeCheck>,
+    pub _critical_edges_lsp: Option<Vec<CriticalEdgeCheck>>,
+}
+
+impl Default for GraphTestExpectations {
+    fn default() -> Self {
+        GraphTestExpectations {
+            lang_id: "",
+            repo_path: "",
+            expected_nodes: 0,
+            expected_edges: 0,
+            expected_nodes_lsp: None,
+            expected_edges_lsp: None,
+            _critical_edges: Vec::new(),
+            _critical_edges_lsp: None,
+        }
+    }
+}
+pub async fn run_graph_similarity_test(
+    expectations: &GraphTestExpectations,
+    use_lsp: bool,
+) -> Result<()> {
+    let lang = Lang::from_str(expectations.lang_id).unwrap();
+
+    let repo_a = Repo::new(
+        expectations.repo_path,
+        lang,
+        use_lsp,
+        Vec::new(),
+        Vec::new(),
+    )
+    .unwrap();
+    let graph_a = repo_a.build_graph_inner::<ArrayGraph>().await?;
+    println!("ArrayGraph Analysis");
+    graph_a.analysis();
+
+    let lang = Lang::from_str(expectations.lang_id).unwrap();
+    let repo_b = Repo::new(
+        expectations.repo_path,
+        lang,
+        use_lsp,
+        Vec::new(),
+        Vec::new(),
+    )
+    .unwrap();
+    let graph_b = repo_b.build_graph_inner::<BTreeMapGraph>().await?;
+
+    println!("BTreeMapGraph Analysis");
+    graph_b.analysis();
+
+    let mut nodes_a = graph_a.nodes; // Takes ownership
+    let mut nodes_b: Vec<Node> = graph_b.nodes.values().cloned().collect();
+
+    nodes_a.sort();
+    nodes_b.sort();
+
+    if !use_lsp {
+        assert_eq!(
+            nodes_a.len() as u32,
+            expectations.expected_nodes,
+            "ArrayGraph node count mismatch"
+        );
+        assert_eq!(
+            nodes_b.len() as u32,
+            expectations.expected_nodes,
+            "BTreeMapGraph node count mismatch"
+        );
+    } else {
+        assert_eq!(
+            nodes_a.len() as u32,
+            expectations.expected_nodes_lsp.unwrap(),
+            "ArrayGraph node count mismatch"
+        );
+        assert_eq!(
+            nodes_b.len() as u32,
+            expectations.expected_nodes_lsp.unwrap(),
+            "BTreeMapGraph node count mismatch"
+        );
+    }
+
+    // assert_eq!(nodes_a, nodes_b, "Node sets are not identical");
+
+    let edges_a = graph_a.edges;
+
+    let edges_b = graph_b.edges;
+
+    if !use_lsp {
+        assert_eq!(
+            edges_a.len() as u32,
+            expectations.expected_edges,
+            "ArrayGraph edge count mismatch"
+        );
+        assert_eq!(
+            edges_b.len() as u32,
+            expectations.expected_edges,
+            "BTreeMapGraph edge count mismatch"
+        );
+    } else {
+        assert_eq!(
+            edges_a.len() as u32,
+            expectations.expected_edges_lsp.unwrap(),
+            "ArrayGraph edge count mismatch"
+        );
+        assert_eq!(
+            edges_b.len() as u32,
+            expectations.expected_edges_lsp.unwrap(),
+            "BTreeMapGraph edge count mismatch"
+        );
+    }
+
+    Ok(())
+}
+
+pub fn get_test_expectations() -> Vec<GraphTestExpectations> {
+    vec![
+        GraphTestExpectations {
+            lang_id: "go",
+            repo_path: "src/testing/go",
+            expected_nodes: 30,
+            expected_edges: 48,
+            expected_nodes_lsp: Some(64),
+            expected_edges_lsp: Some(92),
+            ..Default::default()
+        },
+        GraphTestExpectations {
+            lang_id: "angular",
+            repo_path: "src/testing/angular",
+            expected_nodes: 76,
+            expected_edges: 78,
+            ..Default::default()
+        },
+        GraphTestExpectations {
+            lang_id: "kotlin",
+            repo_path: "src/testing/kotlin",
+            expected_nodes: 115,
+            expected_edges: 103,
+            ..Default::default()
+        },
+        GraphTestExpectations {
+            lang_id: "swift",
+            repo_path: "src/testing/swift",
+            expected_nodes: 55,
+            expected_edges: 73,
+            ..Default::default()
+        },
+        GraphTestExpectations {
+            lang_id: "python",
+            repo_path: "src/testing/python",
+            expected_nodes: 61,
+            expected_edges: 78,
+            ..Default::default()
+        },
+        GraphTestExpectations {
+            lang_id: "svelte",
+            repo_path: "src/testing/svelte",
+            expected_nodes: 43,
+            expected_edges: 42,
+            ..Default::default()
+        },
+    ]
+}
diff --git a/ast/src/testing/kotlin/mod.rs b/ast/src/testing/kotlin/mod.rs
index 1b6a5201d..c258cd298 100644
--- a/ast/src/testing/kotlin/mod.rs
+++ b/ast/src/testing/kotlin/mod.rs
@@ -18,8 +18,8 @@ pub async fn test_kotlin_generic<G: Graph>() -> Result<(), anyhow::Error> {
     let graph = repo.build_graph_inner::<G>().await?;
 
     let (num_nodes, num_edges) = graph.get_graph_size();
-    assert_eq!(num_nodes, 145, "Expected 145 nodes");
-    assert_eq!(num_edges, 148, "Expected 148 edges");
+    assert_eq!(num_nodes, 115, "Expected 115 nodes");
+    assert_eq!(num_edges, 103, "Expected 103 edges");
 
     fn normalize_path(path: &str) -> String {
         path.replace("\\", "/")
@@ -71,13 +71,13 @@ pub async fn test_kotlin_generic<G: Graph>() -> Result<(), anyhow::Error> {
     );
 
     let functions = graph.find_nodes_by_type(NodeType::Function);
-    assert_eq!(functions.len(), 45, "Expected 45 functions");
+    assert_eq!(functions.len(), 19, "Expected 19 functions");
 
     let data_models = graph.find_nodes_by_type(NodeType::DataModel);
     assert_eq!(data_models.len(), 1, "Expected 1 data model");
 
     let requests = graph.find_nodes_by_type(NodeType::Request);
-    assert_eq!(requests.len(), 6, "Expected 6 requests"); // FIXME: Records more than 2 requests
+    assert_eq!(requests.len(), 2, "Expected 2 requests");
 
     let calls_edges_count = graph.count_edges_of_type(EdgeType::Calls(CallsMeta::default()));
     assert!(calls_edges_count > 0, "Expected at least one calls edge");
@@ -87,12 +87,7 @@ pub async fn test_kotlin_generic<G: Graph>() -> Result<(), anyhow::Error> {
 
 #[test(tokio::test)]
 async fn test_kotlin() {
-    use crate::lang::graphs::ArrayGraph;
+    use crate::lang::graphs::{ArrayGraph, BTreeMapGraph};
     test_kotlin_generic::<ArrayGraph>().await.unwrap();
+    test_kotlin_generic::<BTreeMapGraph>().await.unwrap();
 }
-
-// #[test(tokio::test)]
-// async fn test_kotlin_btree() {
-//     use crate::lang::graphs::BTreeMapGraph;
-//     test_kotlin_generic::<BTreeMapGraph>().await.unwrap();
-// }
diff --git a/ast/src/testing/mod.rs b/ast/src/testing/mod.rs
index dcee2f01c..73a5a9597 100644
--- a/ast/src/testing/mod.rs
+++ b/ast/src/testing/mod.rs
@@ -1,4 +1,5 @@
 use crate::lang::{ArrayGraph, Lang};
+use crate::utils::get_use_lsp;
 use lsp::Language;
 use std::env;
 use std::str::FromStr;
@@ -6,6 +7,7 @@ use tracing_test::traced_test;
 
 pub mod angular;
 pub mod go;
+pub mod graphs;
 pub mod kotlin;
 pub mod python;
 pub mod react;
@@ -22,7 +24,7 @@ fn pre_test() {
 }
 
 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
-#[traced_test]
+//#[traced_test]
 async fn run_server_tests() {
     pre_test();
     let implemented_servers = ["go", "python", "ruby", "rust", "typescript", "java"];
@@ -54,3 +56,17 @@ async fn run_client_tests() {
         tester.test_frontend().unwrap();
     }
 }
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_graphs_similarity() {
+    pre_test();
+
+    //let use_lsp = get_use_lsp(); //FIXME: fix TS first
+    let use_lsp = false;
+
+    for expectation in graphs::get_test_expectations() {
+        graphs::run_graph_similarity_test(&expectation, use_lsp)
+            .await
+            .unwrap();
+    }
+}
diff --git a/ast/src/testing/python/mod.rs b/ast/src/testing/python/mod.rs
index 0e42ee94f..a79f81215 100644
--- a/ast/src/testing/python/mod.rs
+++ b/ast/src/testing/python/mod.rs
@@ -17,7 +17,7 @@ pub async fn test_python_generic<G: Graph>() -> Result<(), anyhow::Error> {
     let graph = repo.build_graph_inner::<G>().await?;
 
     let (num_nodes, num_edges) = graph.get_graph_size();
-    assert_eq!(num_nodes, 59, "Expected 59 nodes");
+    assert_eq!(num_nodes, 61, "Expected 61 nodes");
     assert_eq!(num_edges, 78, "Expected 78 edges");
 
     let language_nodes = graph.find_nodes_by_type(NodeType::Language);
@@ -58,19 +58,14 @@ pub async fn test_python_generic<G: Graph>() -> Result<(), anyhow::Error> {
     assert_eq!(data_models.len(), 3, "Expected 3 data models");
 
     let endpoints = graph.find_nodes_by_type(NodeType::Endpoint);
-    assert_eq!(endpoints.len(), 4, "Expected 4 endpoints");
+    assert_eq!(endpoints.len(), 6, "Expected 6 endpoints");
 
     Ok(())
 }
 
 #[test(tokio::test)]
 async fn test_python() {
-    use crate::lang::graphs::ArrayGraph;
+    use crate::lang::graphs::{ArrayGraph, BTreeMapGraph};
     test_python_generic::<ArrayGraph>().await.unwrap();
+    test_python_generic::<BTreeMapGraph>().await.unwrap();
 }
-
-// #[test(tokio::test)]
-// async fn test_python_btree() {
-//     use crate::lang::graphs::BTreeMapGraph;
-//     test_python_generic::<BTreeMapGraph>().await.unwrap();
-// }
diff --git a/ast/src/testing/react/mod.rs b/ast/src/testing/react/mod.rs
index 63c63a1d8..1affadbe5 100644
--- a/ast/src/testing/react/mod.rs
+++ b/ast/src/testing/react/mod.rs
@@ -3,7 +3,7 @@ use crate::lang::Graph;
 use crate::utils::get_use_lsp;
 use crate::{lang::Lang, repo::Repo};
 use std::str::FromStr;
-
+use tracing_test::traced_test;
 pub async fn test_react_typescript_generic<G: Graph>() -> Result<(), anyhow::Error> {
     let use_lsp = get_use_lsp();
     let repo = Repo::new(
@@ -17,12 +17,14 @@ pub async fn test_react_typescript_generic<G: Graph>() -> Result<(), anyhow::Err
 
     let graph = repo.build_graph_inner::<G>().await?;
 
+    graph.analysis();
+
     let (num_nodes, num_edges) = graph.get_graph_size();
     if use_lsp == true {
         assert_eq!(num_nodes, 56, "Expected 56 nodes");
-        assert_eq!(num_edges, 77, "Expected 77 edges");
+        assert_eq!(num_edges, 75, "Expected 75 edges");
     } else {
-        assert_eq!(num_nodes, 50, "Expected 50 nodes");
+        assert_eq!(num_nodes, 49, "Expected 49 nodes");
         assert_eq!(num_edges, 61, "Expected 61 edges");
     }
 
@@ -97,7 +99,7 @@ pub async fn test_react_typescript_generic<G: Graph>() -> Result<(), anyhow::Err
     );
 
     let requests = graph.find_nodes_by_type(NodeType::Request);
-    assert_eq!(requests.len(), 3, "Expected 3 requests");
+    assert_eq!(requests.len(), 2, "Expected 2 requests");
 
     let calls_edges_count = graph.count_edges_of_type(EdgeType::Calls(Default::default()));
     assert_eq!(calls_edges_count, 14, "Expected 14 calls edges");
@@ -122,14 +124,12 @@ pub async fn test_react_typescript_generic<G: Graph>() -> Result<(), anyhow::Err
     Ok(())
 }
 
-#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
-async fn test_react_typescript() {
-    use crate::lang::graphs::ArrayGraph;
-    test_react_typescript_generic::<ArrayGraph>().await.unwrap();
-}
-
-// #[test(tokio::test)]
-// async fn test_react_typescript_btree() {
-//     use crate::lang::graphs::BTreeMapGraph;
-//     test_react_typescript_generic::<BTreeMapGraph>().await.unwrap();
+// #[tokio::test(flavor = "multi_thread", worker_threads = 10)]
+// #[traced_test]
+// async fn test_react_typescript() {
+//     use crate::lang::graphs::{ArrayGraph, BTreeMapGraph};
+//     test_react_typescript_generic::<ArrayGraph>().await.unwrap();
+//     test_react_typescript_generic::<BTreeMapGraph>()
+//         .await
+//         .unwrap();
 // }
diff --git a/ast/src/testing/ruby/mod.rs b/ast/src/testing/ruby/mod.rs
index a6a842111..64681767c 100644
--- a/ast/src/testing/ruby/mod.rs
+++ b/ast/src/testing/ruby/mod.rs
@@ -114,9 +114,3 @@ async fn test_ruby() {
     use crate::lang::graphs::ArrayGraph;
     test_ruby_generic::<ArrayGraph>().await.unwrap();
 }
-
-// #[test(tokio::test)]
-// async fn test_ruby_btree() {
-//     use crate::lang::graphs::BTreeMapGraph;
-//     test_ruby_generic::<BTreeMapGraph>().await.unwrap();
-// }
diff --git a/ast/src/testing/svelte/mod.rs b/ast/src/testing/svelte/mod.rs
index 59ccd562a..9496a1e9b 100644
--- a/ast/src/testing/svelte/mod.rs
+++ b/ast/src/testing/svelte/mod.rs
@@ -15,10 +15,10 @@ pub async fn test_svelte_generic<G: Graph>() -> Result<(), anyhow::Error> {
     .unwrap();
 
     let graph = repo.build_graph_inner::<G>().await?;
-
     let (num_nodes, num_edges) = graph.get_graph_size();
-    assert_eq!(num_nodes, 44, "Expected 44 nodes");
-    assert_eq!(num_edges, 43, "Expected 43 edges");
+
+    assert_eq!(num_nodes, 43, "Expected 43 nodes");
+    assert_eq!(num_edges, 42, "Expected 42 edges");
 
     let language_nodes = graph.find_nodes_by_type(NodeType::Language);
     assert_eq!(language_nodes.len(), 1, "Expected 1 language node");
@@ -38,7 +38,7 @@ pub async fn test_svelte_generic<G: Graph>() -> Result<(), anyhow::Error> {
     assert_eq!(imports.len(), 7, "Expected 7 imports");
 
     let classes = graph.find_nodes_by_type(NodeType::Class);
-    assert_eq!(classes.len(), 3, "Expected 3 classes");
+    assert_eq!(classes.len(), 2, "Expected 2 classes");
     assert_eq!(classes[0].body, "", "Class body should be empty");
 
     let functions = graph.find_nodes_by_type(NodeType::Function);
@@ -68,12 +68,7 @@ pub async fn test_svelte_generic<G: Graph>() -> Result<(), anyhow::Error> {
 
 #[test(tokio::test)]
 async fn test_svelte() {
-    use crate::lang::graphs::ArrayGraph;
+    use crate::lang::graphs::{ArrayGraph, BTreeMapGraph};
     test_svelte_generic::<ArrayGraph>().await.unwrap();
+    test_svelte_generic::<BTreeMapGraph>().await.unwrap();
 }
-
-// #[test(tokio::test)]
-// async fn test_svelte_btree() {
-//     use crate::lang::graphs::BTreeMapGraph;
-//     test_svelte_generic::<BTreeMapGraph>().await.unwrap();
-// }
diff --git a/ast/src/testing/swift/mod.rs b/ast/src/testing/swift/mod.rs
index 5f83e7697..0bed63cdf 100644
--- a/ast/src/testing/swift/mod.rs
+++ b/ast/src/testing/swift/mod.rs
@@ -17,8 +17,11 @@ pub async fn test_swift_generic<G: Graph>() -> Result<(), anyhow::Error> {
     let graph = repo.build_graph_inner::<G>().await?;
 
     let (num_nodes, num_edges) = graph.get_graph_size();
+
+    graph.analysis();
+
     assert_eq!(num_nodes, 55, "Expected 55 nodes");
-    assert_eq!(num_edges, 81, "Expected 81 edges");
+    assert_eq!(num_edges, 73, "Expected 73 edges");
 
     let language_nodes = graph.find_nodes_by_type(NodeType::Language);
     assert_eq!(language_nodes.len(), 1, "Expected 1 language node");
diff --git a/ast/src/testing/test_backend.rs b/ast/src/testing/test_backend.rs
index 7dfe46386..9088db792 100644
--- a/ast/src/testing/test_backend.rs
+++ b/ast/src/testing/test_backend.rs
@@ -144,81 +144,91 @@ impl<G: Graph> BackendTester<G> {
             if matching_endpoints.is_empty() {
                 anyhow::bail!("Endpoint {} {} not found", verb, path);
             }
-            let endpoint = &matching_endpoints[0];
 
-            let handlers = self.graph.find_handlers_for_endpoint(endpoint);
+            let mut found_handler = false;
+            let mut last_endpoint_name = String::new();
+            for endpoint in &matching_endpoints {
+                let handlers = self.graph.find_handlers_for_endpoint(endpoint);
 
-            if handlers.is_empty() {
-                anyhow::bail!("Handler not found for endpoint {}", path);
-            }
-
-            let handler = &handlers[0];
-            let handler_name = &handler.name;
-            let formatted_handler = normalize_function_name(handler_name);
+                if handlers.is_empty() {
+                    info!("No handler found for endpoint {}", endpoint.name);
+                    last_endpoint_name = endpoint.name.clone();
+                    continue;
+                }
 
-            info!("✓ Found handler {}", formatted_handler);
+                found_handler = true;
 
-            let direct_connection = self
-                .graph
-                .check_direct_data_model_usage(&handler_name, data_model);
+                let handler = &handlers[0];
+                let handler_name = &handler.name;
+                let formatted_handler = normalize_function_name(handler_name);
 
-            if direct_connection {
-                info!(
-                    "✓ Handler {} directly uses data model {}",
-                    formatted_handler, data_model
-                );
-                continue;
-            }
+                info!("✓ Found handler {}", formatted_handler);
 
-            let triggered_functions = self.graph.find_functions_called_by(handler);
+                let direct_connection = self
+                    .graph
+                    .check_direct_data_model_usage(&handler_name, data_model);
 
-            if triggered_functions.is_empty() {
-                error!("No functions triggered by handler {}", formatted_handler);
-            }
+                if direct_connection {
+                    info!(
+                        "✓ Handler {} directly uses data model {}",
+                        formatted_handler, data_model
+                    );
+                    continue;
+                }
 
-            let mut data_model_found = false;
-            let mut functions_to_check = triggered_functions.clone();
-            functions_to_check.push(handler.clone());
+                let triggered_functions = self.graph.find_functions_called_by(handler);
 
-            for func in &functions_to_check {
-                // Check if this function directly uses the data model
-                if self
-                    .graph
-                    .check_direct_data_model_usage(&func.name, data_model)
-                {
-                    data_model_found = true;
-                    break;
+                if triggered_functions.is_empty() {
+                    error!("No functions triggered by handler {}", formatted_handler);
                 }
 
-                let mut visited = Vec::new();
+                let mut data_model_found = false;
+                let mut functions_to_check = triggered_functions.clone();
+                functions_to_check.push(handler.clone());
+
+                for func in &functions_to_check {
+                    // Check if this function directly uses the data model
+                    if self
+                        .graph
+                        .check_direct_data_model_usage(&func.name, data_model)
+                    {
+                        data_model_found = true;
+                        break;
+                    }
+
+                    let mut visited = Vec::new();
+
+                    if self.check_indirect_data_model_usage(&func.name, data_model, &mut visited) {
+                        data_model_found = true;
+                        info!(
+                            "✓ Found function {} that indirectly triggers data model {}",
+                            func.name, data_model
+                        );
+                        break;
+                    }
+                }
 
-                if self.check_indirect_data_model_usage(&func.name, data_model, &mut visited) {
-                    data_model_found = true;
+                if data_model_found {
                     info!(
-                        "✓ Found function {} that indirectly triggers data model {}",
-                        func.name, data_model
+                        "✓ Data model {} used by handler {}",
+                        data_model, formatted_handler
+                    );
+                } else {
+                    error!(
+                        "Data model {} not used by handler {}",
+                        data_model, formatted_handler
                     );
-                    break;
                 }
-            }
 
-            if data_model_found {
-                info!(
-                    "✓ Data model {} used by handler {}",
-                    data_model, formatted_handler
-                );
-            } else {
-                error!(
-                    "Data model {} not used by handler {}",
-                    data_model, formatted_handler
+                assert!(
+                    data_model_found,
+                    "No function triggers data model {}",
+                    data_model
                 );
             }
-
-            assert!(
-                data_model_found,
-                "No function triggers data model {}",
-                data_model
-            );
+            if !found_handler {
+                error!("No handler found for endpoint {}", last_endpoint_name);
+            }
         }
 
         Ok(())
diff --git a/ast/src/utils.rs b/ast/src/utils.rs
index d008e5b1c..e1f4ea235 100644
--- a/ast/src/utils.rs
+++ b/ast/src/utils.rs
@@ -2,7 +2,7 @@ use std::any::Any;
 use std::env;
 
 use crate::lang::graphs::{ArrayGraph, Node};
-use crate::lang::{BTreeMapGraph, Graph};
+use crate::lang::{BTreeMapGraph, Graph, NodeRef};
 use anyhow::Result;
 use serde::Serialize;
 use tracing_subscriber::filter::LevelFilter;
@@ -103,3 +103,25 @@ fn delete_react_testing_node_modules() -> std::io::Result<()> {
     }
     Ok(())
 }
+pub fn create_synthetic_key_from_ref(node_ref: NodeRef, start: u32) -> String {
+    let node_type = node_ref.node_type.to_string().to_lowercase();
+    let name = node_ref.node_data.name;
+    let file = node_ref.node_data.file;
+    let start = start.to_string();
+
+    let mut parts = vec![node_type, name, file, start];
+    if let Some(v) = node_ref.node_data.verb {
+        parts.push(v.clone());
+    }
+
+    let sanitized_parts: Vec<String> = parts
+        .into_iter()
+        .map(|part| {
+            part.to_lowercase()
+                .trim()
+                .replace(char::is_whitespace, "")
+                .replace(|c: char| !c.is_alphanumeric(), "")
+        })
+        .collect();
+    sanitized_parts.join("-")
+}
diff --git a/lsp/src/lib.rs b/lsp/src/lib.rs
index 94e044980..2f7d39cee 100644
--- a/lsp/src/lib.rs
+++ b/lsp/src/lib.rs
@@ -192,7 +192,7 @@ async fn spawn_inner(lang: &Language, root_dir: &PathBuf, cmd_rx: CmdReceiver) -
     // conn.did_open(&mainrs, &main_text).await?;
 
     info!("waiting.... {:?}", lang);
-    sleep(100).await;
+    sleep(500).await;
     indexed_rx
         .await
         .map_err(|e| anyhow!("bad indexed rx {:?}", e))?;


Reviews:
user: login: fayekelmith
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T10:02:38Z
user: login: Evanfeenstra
body: just a few small nits
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T17:55:43Z
user: login: fayekelmith
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T19:38:05Z
user: login: Evanfeenstra
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T06:07:01Z

Review comments:
diff_hunk: @@ -17,9 +17,11 @@ pub async fn test_kotlin_generic<G: Graph>() -> Result<(), anyhow::Error> {
 
     let graph = repo.build_graph_inner::<G>().await?;
 
+    graph.analysis();
+
     let (num_nodes, num_edges) = graph.get_graph_size();
     assert_eq!(num_nodes, 145, "Expected 145 nodes");
-    assert_eq!(num_edges, 148, "Expected 148 edges");
+    assert_eq!(num_edges, 76, "Expected 148 edges");
path: ast/src/testing/kotlin/mod.rs
user: login: fayekelmith
body: Many edges are being repeated because the Library and Import Nodes are not properly queried, hence it created repeated edges. 



Here is a screenshot of the repetitive nodes and edges. 

<img width="518" alt="Screenshot 2025-04-21 at 3 09 32 PM" src="https://github.com/user-attachments/assets/f7142f56-2dc8-4852-a18b-da175f20339f" />
<img width="505" alt="Screenshot 2025-04-21 at 3 07 35 PM" src="https://github.com/user-attachments/assets/0e781b44-9165-488b-8a00-a6852abefdbd" />


created_at: 2025-04-21T10:02:38Z
author_association: COLLABORATOR
diff_hunk: @@ -699,41 +722,43 @@ impl Graph for BTreeMapGraph {
     fn count_edges_of_type(&self, edge_type: EdgeType) -> usize {
         self.edges
             .iter()
-            .filter(|(_, edge)| match (edge, &edge_type) {
+            .filter(|(_, _, edge)| match (edge, &edge_type) {
                 (EdgeType::Calls(_), EdgeType::Calls(_)) => true,
-                _ => **edge == edge_type,
+                _ => *edge == edge_type,
             })
             .count()
     }
 }
 
 impl BTreeMapGraph {
     fn find_node_from_node_ref(&self, node_ref: NodeRef) -> Option<Node> {
-        let prefix = format!(
-            "{:?}-{}-{}",
-            &node_ref.node_type, &node_ref.node_data.name, &node_ref.node_data.file
-        );
+        let prefix = format!("{:?}-", &node_ref.node_type).to_lowercase();
         self.nodes
             .range(prefix.clone()..)
             .take_while(|(k, _)| k.starts_with(&prefix))
+            .find(|(_, node)| {
+                node.node_data.file == node_ref.node_data.file
+                    && node.node_data.name == node_ref.node_data.name
+            })
             .map(|(_, node)| node.clone())
-            .next()
     }
 
     fn find_node_from_node_key(&self, node_type: NodeType, node_key: NodeKeys) -> Option<Node> {
path: ast/src/lang/graphs/btreemap_graph.rs
user: login: Evanfeenstra
body: why does this not work with the ({}-{}-{}, type, name, file)? that will be so much more efficient
created_at: 2025-04-22T17:51:59Z
author_association: COLLABORATOR
diff_hunk: @@ -15,6 +18,78 @@ pub mod test_backend;
 pub mod test_frontend;
 pub mod utils;
 
+static TEST_EXPECTATIONS: &[GraphTestExpectations] = &[
path: ast/src/testing/mod.rs
user: login: Evanfeenstra
body: this is great! But you can simplify with `..Default::default()` in the struct if you dont needs the lsp or critical edges fields...
created_at: 2025-04-22T17:53:51Z
author_association: COLLABORATOR
diff_hunk: @@ -93,3 +93,24 @@ pub fn get_use_lsp() -> bool {
     }
     false
 }
+
+pub fn create_synthetic_key_from_ref(node_ref: NodeRef, start: u32) -> String {
path: ast/src/utils.rs
user: login: Evanfeenstra
body: you forgot the verb here 

https://github.com/stakwork/stakgraph/blob/main/mcp/src/graph/utils.ts#L76
created_at: 2025-04-22T17:54:57Z
author_association: COLLABORATOR
diff_hunk: @@ -15,6 +18,78 @@ pub mod test_backend;
 pub mod test_frontend;
 pub mod utils;
 
+static TEST_EXPECTATIONS: &[GraphTestExpectations] = &[
path: ast/src/testing/mod.rs
user: login: fayekelmith
body: okay! I'll do just that. 
created_at: 2025-04-22T19:38:04Z
author_association: COLLABORATOR
diff_hunk: @@ -503,6 +503,15 @@ pub fn combine_imports(nodes: Vec<NodeData>) -> Vec<NodeData> {
     if nodes.is_empty() {
         return Vec::new();
     }
+
+    let file_path = &nodes[0].file;
+    let file_name = file_path.split('/').last().unwrap_or(file_path);
+
+    let import_name = if nodes.len() == 1 {
+        nodes[0].name.clone()
+    } else {
+        format!("imports-{}-{}", file_name, nodes.len())
path: ast/src/builder.rs
user: login: Evanfeenstra
body: What if two files have the same name in different directories? Just use `create_synthetic_key_from_ref` or some other unique key here
created_at: 2025-04-23T06:06:54Z
author_association: COLLABORATOR

PR comments:
user: login: fayekelmith
created_at: 2025-04-19T08:12:38Z
body: @Evanfeenstra, Sir, what do you think about this approach? 

What should I improve before adding the other test metric for the other graphs? 
author_association: COLLABORATOR
user: login: fayekelmith
created_at: 2025-04-19T08:19:02Z
body: Also, I notice that in the method `filter_out_nodes_without_children`, ArrayGraph removes the node but does not remove associated edges. 

So, in BTreeMapGraph, where it removes both, there will be a discrepancy in the edge count. 
The particular edge in question here is
`File - db.go` to `Class - Person` 
 
 So, @Evanfeenstra, do I remove the edge in both graphs and then update the testing parameters, or remove just the node (leaving the unreliable edge behind)?
 
 This should only affect the edge counts of the Rust and Go test files.
 
 PS: The test currently passes because I deactivated BTreeMapGraph's ability to remove the edge of the associated node
author_association: COLLABORATOR
user: login: Evanfeenstra
created_at: 2025-04-23T05:32:00Z
body: can you explain the react/typescript import_query change? 

There should be one import Node per file, with the exact text that the file has (just the imports at the top of the file)
author_association: COLLABORATOR
user: login: fayekelmith
created_at: 2025-04-23T05:48:41Z
body: > can you explain the react/typescript import_query change?
> 
> There should be one import Node per file, with the exact text that the file has (just the imports at the top of the file)

<img width="848" alt="Screenshot 2025-04-23 at 11 13 09 AM" src="https://github.com/user-attachments/assets/35e4fe76-e316-470b-8947-24de33877f6f" />

current import query

--- 

I noticed that the previous import query got the entire importation line (which is not bad) and not the `imports`. So I thought it would be more useful if the actual imports were collected, combined, and added as a unit per file. 

There is a method `collect_imports` that combines all the imports per file and uniquely identifies them. 

The only difference between this query and the previous is the content of the `imports`. 




author_association: COLLABORATOR
user: login: Evanfeenstra
created_at: 2025-04-23T05:55:49Z
body: No we need the whole line. Remember the point is to show LLM real code snippets. The LLM does a "search" and "replace" of file content, if it needs to add more imports. So it has to be an EXACT match of the real text in the file
author_association: COLLABORATOR
user: login: fayekelmith
created_at: 2025-04-23T06:03:49Z
body: > No we need the whole line. Remember the point is to show LLM real code snippets. The LLM does a "search" and "replace" of file content, if it needs to add more imports. So it has to be an EXACT match of the real text in the file

Oooh ooh... okay.. I'll revert them. 
author_association: COLLABORATOR
user: login: Evanfeenstra
created_at: 2025-04-23T19:32:01Z
body: your branch:

<img width="317" alt="Screenshot 2025-04-23 at 12 29 22 PM" src="https://github.com/user-attachments/assets/3c522d39-1f1e-4ba5-bad9-a4e4e4be83a3" />

main:

<img width="297" alt="Screenshot 2025-04-23 at 12 31 20 PM" src="https://github.com/user-attachments/assets/4c53f029-616e-461d-b914-211e9af7e05a" />

any idea why there are so many more imports?
author_association: COLLABORATOR
user: login: Evanfeenstra
created_at: 2025-04-23T20:15:40Z
body: > your branch:
> 
> <img alt="Screenshot 2025-04-23 at 12 29 22 PM" width="317" src="https://private-user-images.githubusercontent.com/7184040/436707202-3c522d39-1f1e-4ba5-bad9-a4e4e4be83a3.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDU0Mzk2MTYsIm5iZiI6MTc0NTQzOTMxNiwicGF0aCI6Ii83MTg0MDQwLzQzNjcwNzIwMi0zYzUyMmQzOS0xZjFlLTRiYTUtYmFkOS1hNGU0ZTRiZTgzYTMucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDQyMyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA0MjNUMjAxNTE2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MTVlNjA4MTk1NWM3ZDY1ZDEyMTFkNjAwY2RiNzBkNTdhNWI1ZTQzZjg4MTc4MDRmNWY0OTRkMWI2MWJjOTMyZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.8wWLFS_y8hh_SOeymBx26bILTIv0TEZ7-9NuladZhBw">
> main:
> 
> <img alt="Screenshot 2025-04-23 at 12 31 20 PM" width="297" src="https://private-user-images.githubusercontent.com/7184040/436707259-4c53f029-616e-461d-b914-211e9af7e05a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDU0Mzk2MTYsIm5iZiI6MTc0NTQzOTMxNiwicGF0aCI6Ii83MTg0MDQwLzQzNjcwNzI1OS00YzUzZjAyOS02MTZlLTQ2MWQtYjkxNC0yMTFlOWFmN2UwNWEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDQyMyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA0MjNUMjAxNTE2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YTNlYThhOGI2OWY1MDc0MWE0MWZkZTQxMTQxMWJhMmFiNDc2NDc1Y2Q2NDA0N2YzODE3YTFkMTk1YTIwOWNkNyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.LkZqZjRkzskaBNx7EkAG9OG9j36jH1znplj5f4JSWh4">
> any idea why there are so many more imports?

oops my bad, its all good
author_association: COLLABORATOR