@@ -296,6 +296,9 @@ static const tool_def_t TOOLS[] = {
296296 "scope trace to a specific parameter name\"},\"edge_types\":{\"type\":\"array\",\"items\":{"
297297 "\"type\":\"string\"}},\"risk_labels\":{\"type\":\"boolean\",\"default\":false,"
298298 "\"description\":\"Add risk classification (CRITICAL/HIGH/MEDIUM/LOW) based on hop distance"
299+ "\"},\"include_tests\":{\"type\":\"boolean\",\"default\":false,"
300+ "\"description\":\"Include test files in results. When false (default), test files are "
301+ "filtered out. When true, test nodes are included with is_test=true marker."
299302 "\"}},\"required\":[\"function_name\",\"project\"]}" },
300303
301304 {"get_code_snippet" ,
@@ -1313,11 +1316,26 @@ static yyjson_doc *resolve_trace_edge_types(const char *args, const char *mode,
13131316 return NULL ;
13141317}
13151318
1316- /* Convert BFS traversal results into a yyjson_mut array of {name, qualified_name, hop}. */
1319+ /* Check if a file path looks like a test file. */
1320+ static bool is_test_file (const char * path ) {
1321+ if (!path ) {
1322+ return false;
1323+ }
1324+ return strstr (path , "/test" ) != NULL || strstr (path , "test_" ) != NULL ||
1325+ strstr (path , "_test." ) != NULL || strstr (path , "/tests/" ) != NULL ||
1326+ strstr (path , "/spec/" ) != NULL || strstr (path , ".test." ) != NULL ;
1327+ }
1328+
1329+ /* Convert BFS traversal results into a yyjson_mut array. */
13171330static yyjson_mut_val * bfs_to_json_array (yyjson_mut_doc * doc , cbm_traverse_result_t * tr ,
1318- bool risk_labels ) {
1331+ bool risk_labels , bool include_tests ) {
13191332 yyjson_mut_val * arr = yyjson_mut_arr (doc );
13201333 for (int i = 0 ; i < tr -> visited_count ; i ++ ) {
1334+ const char * fp = tr -> visited [i ].node .file_path ;
1335+ bool test = is_test_file (fp );
1336+ if (!include_tests && test ) {
1337+ continue ;
1338+ }
13211339 yyjson_mut_val * item = yyjson_mut_obj (doc );
13221340 yyjson_mut_obj_add_str (doc , item , "name" ,
13231341 tr -> visited [i ].node .name ? tr -> visited [i ].node .name : "" );
@@ -1329,6 +1347,9 @@ static yyjson_mut_val *bfs_to_json_array(yyjson_mut_doc *doc, cbm_traverse_resul
13291347 yyjson_mut_obj_add_str (doc , item , "risk" ,
13301348 cbm_risk_label (cbm_hop_to_risk (tr -> visited [i ].hop )));
13311349 }
1350+ if (test ) {
1351+ yyjson_mut_obj_add_bool (doc , item , "is_test" , true);
1352+ }
13321353 yyjson_mut_arr_add_val (arr , item );
13331354 }
13341355 return arr ;
@@ -1343,6 +1364,7 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
13431364 char * param_name = cbm_mcp_get_string_arg (args , "parameter_name" );
13441365 int depth = cbm_mcp_get_int_arg (args , "depth" , MCP_DEFAULT_DEPTH );
13451366 bool risk_labels = cbm_mcp_get_bool_arg (args , "risk_labels" );
1367+ bool include_tests = cbm_mcp_get_bool_arg (args , "include_tests" );
13461368
13471369 if (!func_name ) {
13481370 free (project );
@@ -1419,13 +1441,15 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
14191441 if (do_outbound ) {
14201442 cbm_store_bfs (store , nodes [0 ].id , "outbound" , edge_types , edge_type_count , depth ,
14211443 MCP_BFS_LIMIT , & tr_out );
1422- yyjson_mut_obj_add_val (doc , root , "callees" , bfs_to_json_array (doc , & tr_out , risk_labels ));
1444+ yyjson_mut_obj_add_val (doc , root , "callees" ,
1445+ bfs_to_json_array (doc , & tr_out , risk_labels , include_tests ));
14231446 }
14241447
14251448 if (do_inbound ) {
14261449 cbm_store_bfs (store , nodes [0 ].id , "inbound" , edge_types , edge_type_count , depth ,
14271450 MCP_BFS_LIMIT , & tr_in );
1428- yyjson_mut_obj_add_val (doc , root , "callers" , bfs_to_json_array (doc , & tr_in , risk_labels ));
1451+ yyjson_mut_obj_add_val (doc , root , "callers" ,
1452+ bfs_to_json_array (doc , & tr_in , risk_labels , include_tests ));
14291453 }
14301454
14311455 /* Serialize BEFORE freeing traversal results (yyjson borrows strings) */
0 commit comments