@@ -1002,6 +1002,7 @@ static char *handle_get_graph_schema(cbm_mcp_server_t *srv, const char *args) {
10021002
10031003/* Forward declarations — defined in cross-repo handler section */
10041004static char * handle_cross_repo_search (cbm_mcp_server_t * srv , const char * args );
1005+ static char * handle_cross_repo_trace_call (cbm_mcp_server_t * srv , const char * args , char * func_name );
10051006static char * derive_short_project (const char * full_project );
10061007static void add_trace_steps (yyjson_mut_doc * doc , yyjson_mut_val * parent ,
10071008 const char * key , cbm_cross_trace_step_t * steps , int count );
@@ -1430,8 +1431,18 @@ static char *handle_get_process_steps(cbm_mcp_server_t *srv, const char *args) {
14301431 return result ;
14311432}
14321433
1434+ /* Forward declaration */
1435+ static char * handle_cross_repo_impact (cbm_mcp_server_t * srv , const char * args );
1436+
14331437static char * handle_get_impact (cbm_mcp_server_t * srv , const char * args ) {
14341438 char * project = cbm_mcp_get_string_arg (args , "project" );
1439+
1440+ /* Cross-repo impact: project="*" searches all repos for the target */
1441+ if (project && strcmp (project , "*" ) == 0 ) {
1442+ free (project );
1443+ return handle_cross_repo_impact (srv , args );
1444+ }
1445+
14351446 char * target = cbm_mcp_get_string_arg (args , "target" );
14361447 char * direction = cbm_mcp_get_string_arg (args , "direction" );
14371448 int max_depth = cbm_mcp_get_int_arg (args , "max_depth" , 3 );
@@ -2066,6 +2077,13 @@ static char *handle_get_architecture(cbm_mcp_server_t *srv, const char *args) {
20662077static char * handle_trace_call_path (cbm_mcp_server_t * srv , const char * args ) {
20672078 char * func_name = cbm_mcp_get_string_arg (args , "function_name" );
20682079 char * project = cbm_mcp_get_string_arg (args , "project" );
2080+
2081+ /* Cross-repo trace: project="*" searches all repos for the function */
2082+ if (project && strcmp (project , "*" ) == 0 ) {
2083+ free (project );
2084+ return handle_cross_repo_trace_call (srv , args , func_name );
2085+ }
2086+
20692087 cbm_store_t * store = resolve_store (srv , project );
20702088 char * direction = cbm_mcp_get_string_arg (args , "direction" );
20712089 int depth = cbm_mcp_get_int_arg (args , "depth" , 3 );
@@ -3979,7 +3997,232 @@ static char *handle_generate_embeddings(cbm_mcp_server_t *srv, const char *args)
39793997 return result ;
39803998}
39813999
3982- /* ── build_cross_repo_index ──────────────────────────────────── */
4000+ /* ── Cross-repo get_impact (project="*") ─────────────────────── */
4001+
4002+ static char * handle_cross_repo_impact (cbm_mcp_server_t * srv , const char * args ) {
4003+ (void )srv ;
4004+ char * target = cbm_mcp_get_string_arg (args , "target" );
4005+ char * direction = cbm_mcp_get_string_arg (args , "direction" );
4006+ int max_depth = cbm_mcp_get_int_arg (args , "max_depth" , 3 );
4007+ if (!direction ) direction = heap_strdup ("upstream" );
4008+ if (max_depth <= 0 ) max_depth = 3 ;
4009+
4010+ if (!target || !target [0 ]) {
4011+ free (target ); free (direction );
4012+ return cbm_mcp_text_result ("{\"error\":\"target is required\"}" , true);
4013+ }
4014+
4015+ cbm_cross_repo_t * cr = cbm_cross_repo_open ();
4016+ if (!cr ) {
4017+ free (target ); free (direction );
4018+ return cbm_mcp_text_result (
4019+ "{\"error\":\"Cross-repo index not built. Run build_cross_repo_index first.\"}" , true);
4020+ }
4021+
4022+ /* Find all repos containing the target symbol via BM25 search */
4023+ cbm_cross_search_output_t search_out = {0 };
4024+ cbm_cross_repo_search (cr , target , NULL , 0 , 20 , & search_out );
4025+
4026+ yyjson_mut_doc * doc = yyjson_mut_doc_new (NULL );
4027+ yyjson_mut_val * root = yyjson_mut_obj (doc );
4028+ yyjson_mut_doc_set_root (doc , root );
4029+
4030+ yyjson_mut_obj_add_strcpy (doc , root , "target" , target );
4031+ yyjson_mut_obj_add_strcpy (doc , root , "direction" , direction );
4032+ yyjson_mut_obj_add_bool (doc , root , "cross_repo" , true);
4033+
4034+ bool is_upstream = strcmp (direction , "upstream" ) == 0 ;
4035+ const char * bfs_dir = is_upstream ? "inbound" : "outbound" ;
4036+ const char * edge_types [] = {"CALLS" };
4037+
4038+ char * seen_projects [50 ] = {0 };
4039+ int seen_count = 0 ;
4040+
4041+ yyjson_mut_val * repos_arr = yyjson_mut_arr (doc );
4042+ int total_affected = 0 ;
4043+ const char * max_risk = "LOW" ;
4044+
4045+ for (int si = 0 ; si < search_out .count && seen_count < 50 ; si ++ ) {
4046+ cbm_cross_search_result_t * sr = & search_out .results [si ];
4047+ if (!sr -> project || !sr -> name ) continue ;
4048+ if (strcmp (sr -> name , target ) != 0 ) continue ;
4049+ if (!sr -> label ) continue ;
4050+ if (strcmp (sr -> label , "Function" ) != 0 && strcmp (sr -> label , "Method" ) != 0 &&
4051+ strcmp (sr -> label , "Class" ) != 0 ) continue ;
4052+
4053+ bool seen = false;
4054+ for (int j = 0 ; j < seen_count ; j ++ ) {
4055+ if (seen_projects [j ] && strcmp (seen_projects [j ], sr -> project ) == 0 ) { seen = true; break ; }
4056+ }
4057+ if (seen ) continue ;
4058+ seen_projects [seen_count ++ ] = heap_strdup (sr -> project );
4059+
4060+ char db_path_buf [2048 ];
4061+ project_db_path (sr -> project , db_path_buf , sizeof (db_path_buf ));
4062+ cbm_store_t * pstore = cbm_store_open_path_query (db_path_buf );
4063+ if (!pstore ) continue ;
4064+
4065+ cbm_node_t * nodes = NULL ; int ncount = 0 ;
4066+ cbm_store_find_nodes_by_name (pstore , sr -> project , target , & nodes , & ncount );
4067+
4068+ if (ncount > 0 ) {
4069+ int64_t start_id = nodes [0 ].id ;
4070+ if (nodes [0 ].label && strcmp (nodes [0 ].label , "Class" ) == 0 ) {
4071+ sqlite3 * pdb = cbm_store_get_db (pstore );
4072+ sqlite3_stmt * ms = NULL ;
4073+ sqlite3_prepare_v2 (pdb ,
4074+ "SELECT target_id FROM edges WHERE source_id=?1 AND type='DEFINES_METHOD' LIMIT 1" ,
4075+ -1 , & ms , NULL );
4076+ if (ms ) {
4077+ sqlite3_bind_int64 (ms , 1 , start_id );
4078+ if (sqlite3_step (ms ) == SQLITE_ROW ) start_id = sqlite3_column_int64 (ms , 0 );
4079+ sqlite3_finalize (ms );
4080+ }
4081+ }
4082+
4083+ cbm_traverse_result_t tr = {0 };
4084+ cbm_store_bfs (pstore , start_id , bfs_dir , edge_types , 1 , max_depth , 50 , & tr );
4085+
4086+ yyjson_mut_val * repo_item = yyjson_mut_obj (doc );
4087+ char * short_proj = derive_short_project (sr -> project );
4088+ yyjson_mut_obj_add_strcpy (doc , repo_item , "project" , short_proj ? short_proj : "" );
4089+ yyjson_mut_obj_add_strcpy (doc , repo_item , "project_id" , sr -> project );
4090+ free (short_proj );
4091+ yyjson_mut_obj_add_strcpy (doc , repo_item , "file_path" , sr -> file_path ? sr -> file_path : "" );
4092+ yyjson_mut_obj_add_int (doc , repo_item , "total_affected" , tr .visited_count );
4093+
4094+ int d1 = 0 ;
4095+ yyjson_mut_val * d1_arr = yyjson_mut_arr (doc );
4096+ for (int vi = 0 ; vi < tr .visited_count ; vi ++ ) {
4097+ if (tr .visited [vi ].hop == 1 ) {
4098+ d1 ++ ;
4099+ yyjson_mut_val * h = yyjson_mut_obj (doc );
4100+ yyjson_mut_obj_add_strcpy (doc , h , "name" ,
4101+ tr .visited [vi ].node .name ? tr .visited [vi ].node .name : "" );
4102+ yyjson_mut_obj_add_strcpy (doc , h , "label" ,
4103+ tr .visited [vi ].node .label ? tr .visited [vi ].node .label : "" );
4104+ yyjson_mut_obj_add_strcpy (doc , h , "file_path" ,
4105+ tr .visited [vi ].node .file_path ? tr .visited [vi ].node .file_path : "" );
4106+ yyjson_mut_arr_add_val (d1_arr , h );
4107+ }
4108+ }
4109+ yyjson_mut_obj_add_val (doc , repo_item , "d1_will_break" , d1_arr );
4110+
4111+ const char * risk = d1 >= 20 ? "CRITICAL" : d1 >= 10 ? "HIGH" : d1 >= 3 ? "MEDIUM" : "LOW" ;
4112+ yyjson_mut_obj_add_str (doc , repo_item , "risk" , risk );
4113+ if (strcmp (risk , "CRITICAL" ) == 0 ||
4114+ (strcmp (risk , "HIGH" ) == 0 && strcmp (max_risk , "CRITICAL" ) != 0 ) ||
4115+ (strcmp (risk , "MEDIUM" ) == 0 && strcmp (max_risk , "LOW" ) == 0 ))
4116+ max_risk = risk ;
4117+ total_affected += tr .visited_count ;
4118+
4119+ yyjson_mut_arr_add_val (repos_arr , repo_item );
4120+ cbm_store_traverse_free (& tr );
4121+ }
4122+ cbm_store_free_nodes (nodes , ncount );
4123+ cbm_store_close (pstore );
4124+ }
4125+
4126+ yyjson_mut_obj_add_str (doc , root , "risk" , max_risk );
4127+ yyjson_mut_obj_add_int (doc , root , "total_affected" , total_affected );
4128+ yyjson_mut_obj_add_int (doc , root , "repos_with_target" , seen_count );
4129+ yyjson_mut_obj_add_val (doc , root , "repos" , repos_arr );
4130+
4131+ char * json = yy_doc_to_str (doc );
4132+ yyjson_mut_doc_free (doc );
4133+ cbm_cross_search_free (& search_out );
4134+ cbm_cross_repo_close (cr );
4135+ for (int i = 0 ; i < seen_count ; i ++ ) free (seen_projects [i ]);
4136+ free (target ); free (direction );
4137+ char * result = cbm_mcp_text_result (json , false);
4138+ free (json );
4139+ return result ;
4140+ }
4141+
4142+ /* ── Cross-repo trace_call_path (project="*") ────────────────── */
4143+
4144+ static char * handle_cross_repo_trace_call (cbm_mcp_server_t * srv , const char * args ,
4145+ char * func_name ) {
4146+ (void )srv ;
4147+ if (!func_name || !func_name [0 ]) {
4148+ free (func_name );
4149+ return cbm_mcp_text_result ("{\"error\":\"function_name is required\"}" , true);
4150+ }
4151+
4152+ cbm_cross_repo_t * cr = cbm_cross_repo_open ();
4153+ if (!cr ) {
4154+ free (func_name );
4155+ return cbm_mcp_text_result (
4156+ "{\"error\":\"Cross-repo index not built. Run build_cross_repo_index first.\"}" , true);
4157+ }
4158+
4159+ cbm_cross_search_output_t search_out = {0 };
4160+ cbm_cross_repo_search (cr , func_name , NULL , 0 , 30 , & search_out );
4161+
4162+ yyjson_mut_doc * doc = yyjson_mut_doc_new (NULL );
4163+ yyjson_mut_val * root = yyjson_mut_obj (doc );
4164+ yyjson_mut_doc_set_root (doc , root );
4165+ yyjson_mut_obj_add_strcpy (doc , root , "function_name" , func_name );
4166+ yyjson_mut_obj_add_bool (doc , root , "cross_repo" , true);
4167+
4168+ char * seen_projects [50 ] = {0 };
4169+ int seen_count = 0 ;
4170+ yyjson_mut_val * repos_arr = yyjson_mut_arr (doc );
4171+
4172+ for (int si = 0 ; si < search_out .count && seen_count < 50 ; si ++ ) {
4173+ cbm_cross_search_result_t * sr = & search_out .results [si ];
4174+ if (!sr -> project || !sr -> name ) continue ;
4175+ if (strcmp (sr -> name , func_name ) != 0 ) continue ;
4176+ if (!sr -> label ) continue ;
4177+ if (strcmp (sr -> label , "Function" ) != 0 && strcmp (sr -> label , "Method" ) != 0 &&
4178+ strcmp (sr -> label , "Class" ) != 0 ) continue ;
4179+
4180+ bool seen = false;
4181+ for (int j = 0 ; j < seen_count ; j ++ ) {
4182+ if (seen_projects [j ] && strcmp (seen_projects [j ], sr -> project ) == 0 ) { seen = true; break ; }
4183+ }
4184+ if (seen ) continue ;
4185+ seen_projects [seen_count ++ ] = heap_strdup (sr -> project );
4186+
4187+ char db_path_buf [2048 ];
4188+ project_db_path (sr -> project , db_path_buf , sizeof (db_path_buf ));
4189+
4190+ yyjson_mut_val * repo_item = yyjson_mut_obj (doc );
4191+ char * short_proj = derive_short_project (sr -> project );
4192+ yyjson_mut_obj_add_strcpy (doc , repo_item , "project" , short_proj ? short_proj : "" );
4193+ yyjson_mut_obj_add_strcpy (doc , repo_item , "project_id" , sr -> project );
4194+ free (short_proj );
4195+ yyjson_mut_obj_add_strcpy (doc , repo_item , "file_path" , sr -> file_path ? sr -> file_path : "" );
4196+ yyjson_mut_obj_add_strcpy (doc , repo_item , "label" , sr -> label );
4197+
4198+ cbm_cross_trace_step_t * in_steps = NULL ; int in_count = 0 ;
4199+ cbm_cross_repo_trace_in_project (db_path_buf , func_name , sr -> file_path ,
4200+ NULL , "inbound" , 2 , & in_steps , & in_count );
4201+ if (in_count > 0 ) add_trace_steps (doc , repo_item , "incoming_calls" , in_steps , in_count );
4202+ cbm_cross_trace_free (in_steps , in_count );
4203+
4204+ cbm_cross_trace_step_t * out_steps = NULL ; int out_count = 0 ;
4205+ cbm_cross_repo_trace_in_project (db_path_buf , func_name , sr -> file_path ,
4206+ NULL , "outbound" , 2 , & out_steps , & out_count );
4207+ if (out_count > 0 ) add_trace_steps (doc , repo_item , "outgoing_calls" , out_steps , out_count );
4208+ cbm_cross_trace_free (out_steps , out_count );
4209+
4210+ yyjson_mut_arr_add_val (repos_arr , repo_item );
4211+ }
4212+
4213+ yyjson_mut_obj_add_int (doc , root , "repos_with_function" , seen_count );
4214+ yyjson_mut_obj_add_val (doc , root , "repos" , repos_arr );
4215+
4216+ char * json = yy_doc_to_str (doc );
4217+ yyjson_mut_doc_free (doc );
4218+ cbm_cross_search_free (& search_out );
4219+ cbm_cross_repo_close (cr );
4220+ for (int i = 0 ; i < seen_count ; i ++ ) free (seen_projects [i ]);
4221+ free (func_name );
4222+ char * result = cbm_mcp_text_result (json , false);
4223+ free (json );
4224+ return result ;
4225+ }
39834226
39844227/* ── Cross-repo search (search_graph with project="*") ───────── */
39854228
0 commit comments