@@ -1660,9 +1660,53 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
16601660 cbm_store_find_nodes_by_name (store , project , func_name , & nodes , & node_count );
16611661
16621662 if (node_count == 0 ) {
1663- free (func_name );
1664- free (project );
1665- free (direction );
1663+ /* Fuzzy fallback: try substring match when exact name not found.
1664+ * This handles cases like searching for "RecordingSession" when only
1665+ * "ContinuousRecordingSessionDataGen" exists. */
1666+ cbm_search_params_t fuzzy = {0 };
1667+ char pattern [512 ];
1668+ snprintf (pattern , sizeof (pattern ), ".*%s.*" , func_name );
1669+ fuzzy .project = project ;
1670+ fuzzy .name_pattern = pattern ;
1671+ fuzzy .limit = 10 ;
1672+ cbm_search_output_t fuzzy_results = {0 };
1673+ cbm_store_search (store , & fuzzy , & fuzzy_results );
1674+
1675+ if (fuzzy_results .count > 0 ) {
1676+ /* Return fuzzy matches as suggestions */
1677+ yyjson_mut_doc * fdoc = yyjson_mut_doc_new (NULL );
1678+ yyjson_mut_val * froot = yyjson_mut_obj (fdoc );
1679+ yyjson_mut_doc_set_root (fdoc , froot );
1680+ yyjson_mut_obj_add_str (fdoc , froot , "status" , "not_found_exact" );
1681+ char msg [512 ];
1682+ snprintf (msg , sizeof (msg ),
1683+ "No exact match for '%s'. Found %d partial matches — "
1684+ "use one of these exact names:" , func_name , fuzzy_results .count );
1685+ yyjson_mut_obj_add_strcpy (fdoc , froot , "message" , msg );
1686+ yyjson_mut_val * suggestions = yyjson_mut_arr (fdoc );
1687+ for (int i = 0 ; i < fuzzy_results .count ; i ++ ) {
1688+ yyjson_mut_val * si = yyjson_mut_obj (fdoc );
1689+ yyjson_mut_obj_add_strcpy (fdoc , si , "name" ,
1690+ fuzzy_results .results [i ].node .name ? fuzzy_results .results [i ].node .name : "" );
1691+ yyjson_mut_obj_add_strcpy (fdoc , si , "label" ,
1692+ fuzzy_results .results [i ].node .label ? fuzzy_results .results [i ].node .label : "" );
1693+ yyjson_mut_obj_add_strcpy (fdoc , si , "file_path" ,
1694+ fuzzy_results .results [i ].node .file_path ? fuzzy_results .results [i ].node .file_path : "" );
1695+ yyjson_mut_obj_add_int (fdoc , si , "line" , fuzzy_results .results [i ].node .start_line );
1696+ yyjson_mut_arr_add_val (suggestions , si );
1697+ }
1698+ yyjson_mut_obj_add_val (fdoc , froot , "suggestions" , suggestions );
1699+ char * fjson = yy_doc_to_str (fdoc );
1700+ yyjson_mut_doc_free (fdoc );
1701+ cbm_store_search_free (& fuzzy_results );
1702+ free (func_name ); free (project ); free (direction );
1703+ cbm_store_free_nodes (nodes , 0 );
1704+ char * result = cbm_mcp_text_result (fjson , false);
1705+ free (fjson );
1706+ return result ;
1707+ }
1708+ cbm_store_search_free (& fuzzy_results );
1709+ free (func_name ); free (project ); free (direction );
16661710 cbm_store_free_nodes (nodes , 0 );
16671711 return cbm_mcp_text_result ("{\"error\":\"function not found\"}" , true);
16681712 }
@@ -1792,6 +1836,25 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
17921836 yyjson_mut_obj_add_val (doc , root , "candidates" , cands );
17931837 }
17941838
1839+ /* Check if the node has any edges at all. If not, return basic info only.
1840+ * This avoids BFS crashes on nodes with 0 edges (e.g. Type nodes, empty Classes). */
1841+ {
1842+ int in_deg = 0 ;
1843+ int out_deg = 0 ;
1844+ cbm_store_node_degree (store , nodes [best_idx ].id , & in_deg , & out_deg );
1845+ if (in_deg == 0 && out_deg == 0 ) {
1846+ /* No edges — return basic info */
1847+ char * json = yy_doc_to_str (doc );
1848+ yyjson_mut_doc_free (doc );
1849+ free (start_ids );
1850+ cbm_store_free_nodes (nodes , node_count );
1851+ free (func_name ); free (project ); free (direction );
1852+ char * result = cbm_mcp_text_result (json , false);
1853+ free (json );
1854+ return result ;
1855+ }
1856+ }
1857+
17951858 /* ── Categorized edge query: like GitNexus context() ──
17961859 * Instead of flat BFS, query each edge type separately and return
17971860 * categorized results: incoming.calls, incoming.imports, incoming.extends,
@@ -1810,8 +1873,7 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
18101873
18111874 /* Collect all traversal results for lifetime management */
18121875 #define MAX_TR 64
1813- cbm_traverse_result_t all_tr [MAX_TR ];
1814- memset (all_tr , 0 , sizeof (all_tr ));
1876+ cbm_traverse_result_t * all_tr = calloc (MAX_TR , sizeof (cbm_traverse_result_t ));
18151877 int tr_count = 0 ;
18161878
18171879 if (do_inbound ) {
@@ -2047,6 +2109,7 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
20472109 for (int t = 0 ; t < tr_count ; t ++ ) {
20482110 cbm_store_traverse_free (& all_tr [t ]);
20492111 }
2112+ free (all_tr );
20502113 #undef EDGE_QUERY_MAX
20512114 #undef MAX_TR
20522115
0 commit comments