| 6396 | } |
| 6397 | |
| 6398 | static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) { |
| 6399 | char *func_name = cbm_mcp_get_string_arg(args, "function_name"); |
| 6400 | char *project = get_project_arg(args); |
| 6401 | cbm_store_t *store = resolve_store(srv, project); |
| 6402 | char *direction = cbm_mcp_get_string_arg(args, "direction"); |
| 6403 | char *mode = cbm_mcp_get_string_arg(args, "mode"); |
| 6404 | char *param_name = cbm_mcp_get_string_arg(args, "parameter_name"); |
| 6405 | int depth = cbm_mcp_get_int_arg(args, "depth", MCP_DEFAULT_DEPTH); |
| 6406 | depth = clamp_mcp_depth(depth, "trace_call_path"); |
| 6407 | /* Per-direction node budget for the BFS working set. The old fixed |
| 6408 | * MCP_BFS_LIMIT silently truncated hub traces at 100 nodes with no |
| 6409 | * signal; now the limit is a documented parameter and hitting it emits |
| 6410 | * `truncated: true` (never a silent truncation — same policy as the |
| 6411 | * depth clamp, #887). */ |
| 6412 | int trace_limit = cbm_mcp_get_int_arg(args, "limit", MCP_BFS_LIMIT); |
| 6413 | if (trace_limit < 1) { |
| 6414 | trace_limit = 1; |
| 6415 | } |
| 6416 | if (trace_limit > MCP_BFS_LIMIT_MAX) { |
| 6417 | trace_limit = MCP_BFS_LIMIT_MAX; |
| 6418 | } |
| 6419 | bool risk_labels = cbm_mcp_get_bool_arg(args, "risk_labels"); |
| 6420 | bool include_tests = cbm_mcp_get_bool_arg(args, "include_tests"); |
| 6421 | /* Off by default: two extra columns on every row is exactly the kind of |
| 6422 | * inflation the tree format exists to avoid. Opt in when you need to judge |
| 6423 | * whether an edge is trustworthy. */ |
| 6424 | bool include_evidence = cbm_mcp_get_bool_arg(args, "include_evidence"); |
| 6425 | |
| 6426 | if (!func_name) { |
| 6427 | free(project); |
| 6428 | free(direction); |
| 6429 | free(mode); |
| 6430 | free(param_name); |
| 6431 | return cbm_mcp_text_result("function_name is required", true); |
| 6432 | } |
| 6433 | if (!store) { |
| 6434 | char *_err = build_project_list_error("project not found or not indexed"); |
| 6435 | char *_res = cbm_mcp_text_result(_err, true); |
| 6436 | free(_err); |
| 6437 | free(func_name); |
| 6438 | free(project); |
| 6439 | free(direction); |
| 6440 | free(mode); |
| 6441 | free(param_name); |
| 6442 | return _res; |
| 6443 | } |
| 6444 | |
| 6445 | char *not_indexed = verify_project_indexed(store, project); |
| 6446 | if (not_indexed) { |
| 6447 | free(func_name); |
| 6448 | free(project); |
| 6449 | free(direction); |
| 6450 | free(mode); |
| 6451 | free(param_name); |
| 6452 | return not_indexed; |
| 6453 | } |
| 6454 | |
| 6455 | /* Pagination: decode + validate the resume cursor (if any) against the |
no test coverage detected