@@ -1265,15 +1265,94 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
12651265 return cbm_mcp_text_result ("{\"error\":\"function not found\"}" , true);
12661266 }
12671267
1268+ /* Pick the best node for tracing. Strategy:
1269+ * 1. Prefer Function/Method nodes that are NOT constructors (same name as a
1270+ * Class in the result set — constructors rarely have interesting CALLS).
1271+ * 2. If only Class/Interface nodes match, resolve through DEFINES_METHOD. */
1272+ int best_idx = 0 ;
1273+ bool has_class = false;
1274+ int class_idx = -1 ;
1275+ for (int i = 0 ; i < node_count ; i ++ ) {
1276+ const char * lbl = nodes [i ].label ;
1277+ if (lbl && (strcmp (lbl , "Class" ) == 0 || strcmp (lbl , "Interface" ) == 0 )) {
1278+ has_class = true;
1279+ if (class_idx < 0 ) class_idx = i ;
1280+ }
1281+ }
1282+ /* Look for a non-constructor Function/Method */
1283+ bool found_callable = false;
1284+ for (int i = 0 ; i < node_count ; i ++ ) {
1285+ const char * lbl = nodes [i ].label ;
1286+ if (lbl && (strcmp (lbl , "Function" ) == 0 || strcmp (lbl , "Method" ) == 0 )) {
1287+ /* Skip if this is a constructor (same name as a Class in results) */
1288+ if (has_class ) continue ;
1289+ best_idx = i ;
1290+ found_callable = true;
1291+ break ;
1292+ }
1293+ }
1294+ /* If no non-constructor callable was found but we have a Class, use the Class */
1295+ if (!found_callable && class_idx >= 0 ) {
1296+ best_idx = class_idx ;
1297+ }
1298+
1299+ /* Determine if the selected node is a Class or Interface. If so, we need to
1300+ * resolve through DEFINES_METHOD edges to find the actual callable methods,
1301+ * then run BFS from each method and merge results. */
1302+ bool is_class_like = false;
1303+ const char * best_label = nodes [best_idx ].label ;
1304+ if (best_label &&
1305+ (strcmp (best_label , "Class" ) == 0 || strcmp (best_label , "Interface" ) == 0 )) {
1306+ is_class_like = true;
1307+ }
1308+
1309+ /* Collect BFS start IDs: either the single node, or all methods of the class */
1310+ int64_t * start_ids = NULL ;
1311+ int start_id_count = 0 ;
1312+
1313+ if (is_class_like ) {
1314+ /* Find all DEFINES_METHOD targets of this class */
1315+ cbm_edge_t * dm_edges = NULL ;
1316+ int dm_count = 0 ;
1317+ cbm_store_find_edges_by_source_type (store , nodes [best_idx ].id , "DEFINES_METHOD" ,
1318+ & dm_edges , & dm_count );
1319+ if (dm_count > 0 ) {
1320+ start_ids = malloc ((size_t )dm_count * sizeof (int64_t ));
1321+ for (int i = 0 ; i < dm_count ; i ++ ) {
1322+ start_ids [i ] = dm_edges [i ].target_id ;
1323+ }
1324+ start_id_count = dm_count ;
1325+ }
1326+ /* Free edge data */
1327+ for (int i = 0 ; i < dm_count ; i ++ ) {
1328+ free ((void * )dm_edges [i ].project );
1329+ free ((void * )dm_edges [i ].type );
1330+ free ((void * )dm_edges [i ].properties_json );
1331+ }
1332+ free (dm_edges );
1333+
1334+ /* If no methods found, fall back to the class node itself */
1335+ if (start_id_count == 0 ) {
1336+ start_ids = malloc (sizeof (int64_t ));
1337+ start_ids [0 ] = nodes [best_idx ].id ;
1338+ start_id_count = 1 ;
1339+ }
1340+ } else {
1341+ start_ids = malloc (sizeof (int64_t ));
1342+ start_ids [0 ] = nodes [best_idx ].id ;
1343+ start_id_count = 1 ;
1344+ }
1345+
12681346 yyjson_mut_doc * doc = yyjson_mut_doc_new (NULL );
12691347 yyjson_mut_val * root = yyjson_mut_obj (doc );
12701348 yyjson_mut_doc_set_root (doc , root );
12711349
12721350 yyjson_mut_obj_add_str (doc , root , "function" , func_name );
12731351 yyjson_mut_obj_add_str (doc , root , "direction" , direction );
12741352
1275- const char * edge_types [] = {"CALLS" };
1276- int edge_type_count = 1 ;
1353+ /* Include HTTP_CALLS and ASYNC_CALLS alongside CALLS for broader coverage */
1354+ const char * edge_types [] = {"CALLS" , "HTTP_CALLS" , "ASYNC_CALLS" };
1355+ int edge_type_count = 3 ;
12771356
12781357 /* Run BFS for each requested direction.
12791358 * IMPORTANT: yyjson_mut_obj_add_str borrows pointers — we must keep
@@ -1283,41 +1362,59 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
12831362 // NOLINTNEXTLINE(readability-implicit-bool-conversion)
12841363 bool do_inbound = strcmp (direction , "inbound" ) == 0 || strcmp (direction , "both" ) == 0 ;
12851364
1286- cbm_traverse_result_t tr_out = {0 };
1287- cbm_traverse_result_t tr_in = {0 };
1365+ /* For class resolution, we run BFS from each method and merge results.
1366+ * We keep all traversal results alive until after JSON serialization. */
1367+ cbm_traverse_result_t * all_tr_out = NULL ;
1368+ cbm_traverse_result_t * all_tr_in = NULL ;
1369+ int tr_out_count = 0 ;
1370+ int tr_in_count = 0 ;
12881371
12891372 if (do_outbound ) {
1290- cbm_store_bfs ( store , nodes [ 0 ]. id , "outbound" , edge_types , edge_type_count , depth , 100 ,
1291- & tr_out ) ;
1373+ all_tr_out = calloc (( size_t ) start_id_count , sizeof ( cbm_traverse_result_t ));
1374+ tr_out_count = start_id_count ;
12921375
12931376 yyjson_mut_val * callees = yyjson_mut_arr (doc );
1294- for (int i = 0 ; i < tr_out .visited_count ; i ++ ) {
1295- yyjson_mut_val * item = yyjson_mut_obj (doc );
1296- yyjson_mut_obj_add_str (doc , item , "name" ,
1297- tr_out .visited [i ].node .name ? tr_out .visited [i ].node .name : "" );
1298- yyjson_mut_obj_add_str (
1299- doc , item , "qualified_name" ,
1300- tr_out .visited [i ].node .qualified_name ? tr_out .visited [i ].node .qualified_name : "" );
1301- yyjson_mut_obj_add_int (doc , item , "hop" , tr_out .visited [i ].hop );
1302- yyjson_mut_arr_add_val (callees , item );
1377+ for (int s = 0 ; s < start_id_count ; s ++ ) {
1378+ cbm_store_bfs (store , start_ids [s ], "outbound" , edge_types , edge_type_count , depth , 100 ,
1379+ & all_tr_out [s ]);
1380+ for (int i = 0 ; i < all_tr_out [s ].visited_count ; i ++ ) {
1381+ yyjson_mut_val * item = yyjson_mut_obj (doc );
1382+ yyjson_mut_obj_add_str (
1383+ doc , item , "name" ,
1384+ all_tr_out [s ].visited [i ].node .name ? all_tr_out [s ].visited [i ].node .name : "" );
1385+ yyjson_mut_obj_add_str (
1386+ doc , item , "qualified_name" ,
1387+ all_tr_out [s ].visited [i ].node .qualified_name
1388+ ? all_tr_out [s ].visited [i ].node .qualified_name
1389+ : "" );
1390+ yyjson_mut_obj_add_int (doc , item , "hop" , all_tr_out [s ].visited [i ].hop );
1391+ yyjson_mut_arr_add_val (callees , item );
1392+ }
13031393 }
13041394 yyjson_mut_obj_add_val (doc , root , "callees" , callees );
13051395 }
13061396
13071397 if (do_inbound ) {
1308- cbm_store_bfs ( store , nodes [ 0 ]. id , "inbound" , edge_types , edge_type_count , depth , 100 ,
1309- & tr_in ) ;
1398+ all_tr_in = calloc (( size_t ) start_id_count , sizeof ( cbm_traverse_result_t ));
1399+ tr_in_count = start_id_count ;
13101400
13111401 yyjson_mut_val * callers = yyjson_mut_arr (doc );
1312- for (int i = 0 ; i < tr_in .visited_count ; i ++ ) {
1313- yyjson_mut_val * item = yyjson_mut_obj (doc );
1314- yyjson_mut_obj_add_str (doc , item , "name" ,
1315- tr_in .visited [i ].node .name ? tr_in .visited [i ].node .name : "" );
1316- yyjson_mut_obj_add_str (
1317- doc , item , "qualified_name" ,
1318- tr_in .visited [i ].node .qualified_name ? tr_in .visited [i ].node .qualified_name : "" );
1319- yyjson_mut_obj_add_int (doc , item , "hop" , tr_in .visited [i ].hop );
1320- yyjson_mut_arr_add_val (callers , item );
1402+ for (int s = 0 ; s < start_id_count ; s ++ ) {
1403+ cbm_store_bfs (store , start_ids [s ], "inbound" , edge_types , edge_type_count , depth , 100 ,
1404+ & all_tr_in [s ]);
1405+ for (int i = 0 ; i < all_tr_in [s ].visited_count ; i ++ ) {
1406+ yyjson_mut_val * item = yyjson_mut_obj (doc );
1407+ yyjson_mut_obj_add_str (
1408+ doc , item , "name" ,
1409+ all_tr_in [s ].visited [i ].node .name ? all_tr_in [s ].visited [i ].node .name : "" );
1410+ yyjson_mut_obj_add_str (
1411+ doc , item , "qualified_name" ,
1412+ all_tr_in [s ].visited [i ].node .qualified_name
1413+ ? all_tr_in [s ].visited [i ].node .qualified_name
1414+ : "" );
1415+ yyjson_mut_obj_add_int (doc , item , "hop" , all_tr_in [s ].visited [i ].hop );
1416+ yyjson_mut_arr_add_val (callers , item );
1417+ }
13211418 }
13221419 yyjson_mut_obj_add_val (doc , root , "callers" , callers );
13231420 }
@@ -1327,13 +1424,16 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
13271424 yyjson_mut_doc_free (doc );
13281425
13291426 /* Now safe to free traversal data */
1330- if ( do_outbound ) {
1331- cbm_store_traverse_free (& tr_out );
1427+ for ( int s = 0 ; s < tr_out_count ; s ++ ) {
1428+ cbm_store_traverse_free (& all_tr_out [ s ] );
13321429 }
1333- if (do_inbound ) {
1334- cbm_store_traverse_free (& tr_in );
1430+ free (all_tr_out );
1431+ for (int s = 0 ; s < tr_in_count ; s ++ ) {
1432+ cbm_store_traverse_free (& all_tr_in [s ]);
13351433 }
1434+ free (all_tr_in );
13361435
1436+ free (start_ids );
13371437 cbm_store_free_nodes (nodes , node_count );
13381438 free (func_name );
13391439 free (project );
0 commit comments