| 6330 | } |
| 6331 | |
| 6332 | static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { |
| 6333 | char *func_name = cbm_mcp_get_string_arg(args, "function_name"); |
| 6334 | char *project = get_project_arg(args); |
| 6335 | cbm_store_t *store = resolve_store(srv, project); |
| 6336 | char *direction = cbm_mcp_get_string_arg(args, "direction"); |
| 6337 | char *mode = cbm_mcp_get_string_arg(args, "mode"); |
| 6338 | char *param_name = cbm_mcp_get_string_arg(args, "parameter_name"); |
| 6339 | int depth = cbm_mcp_get_int_arg(args, "depth", MCP_DEFAULT_DEPTH); |
| 6340 | depth = clamp_mcp_depth(depth, "trace_call_path"); |
| 6341 | /* Per-direction node budget for the BFS working set. The old fixed |
| 6342 | * MCP_BFS_LIMIT silently truncated hub traces at 100 nodes with no |
| 6343 | * signal; now the limit is a documented parameter and hitting it emits |
| 6344 | * `truncated: true` (never a silent truncation — same policy as the |
| 6345 | * depth clamp, #887). */ |
| 6346 | int trace_limit = cbm_mcp_get_int_arg(args, "limit", MCP_BFS_LIMIT); |
| 6347 | if (trace_limit < 1) { |
| 6348 | trace_limit = 1; |
| 6349 | } |
| 6350 | if (trace_limit > MCP_BFS_LIMIT_MAX) { |
| 6351 | trace_limit = MCP_BFS_LIMIT_MAX; |
| 6352 | } |
| 6353 | bool risk_labels = cbm_mcp_get_bool_arg(args, "risk_labels"); |
| 6354 | bool include_tests = cbm_mcp_get_bool_arg(args, "include_tests"); |
| 6355 | /* Off by default: two extra columns on every row is exactly the kind of |
| 6356 | * inflation the tree format exists to avoid. Opt in when you need to judge |
| 6357 | * whether an edge is trustworthy. */ |
| 6358 | bool include_evidence = cbm_mcp_get_bool_arg(args, "include_evidence"); |
| 6359 | |
| 6360 | if (!func_name) { |
| 6361 | free(project); |
| 6362 | free(direction); |
| 6363 | free(mode); |
| 6364 | free(param_name); |
| 6365 | return cbm_mcp_text_result("function_name is required", true); |
| 6366 | } |
| 6367 | if (!store) { |
| 6368 | char *_err = build_project_list_error("project not found or not indexed"); |
| 6369 | char *_res = cbm_mcp_text_result(_err, true); |
| 6370 | free(_err); |
| 6371 | free(func_name); |
| 6372 | free(project); |
| 6373 | free(direction); |
| 6374 | free(mode); |
| 6375 | free(param_name); |
| 6376 | return _res; |
| 6377 | } |
| 6378 | |
| 6379 | char *not_indexed = verify_project_indexed(store, project); |
| 6380 | if (not_indexed) { |
| 6381 | free(func_name); |
| 6382 | free(project); |
| 6383 | free(direction); |
| 6384 | free(mode); |
| 6385 | free(param_name); |
| 6386 | return not_indexed; |
| 6387 | } |
| 6388 | |
| 6389 | /* Pagination: decode + validate the resume cursor (if any) against the |
no test coverage detected