Parse a targeted check_index_coverage envelope and return a compact note for * the requested path. *is_error is set for MCP errors (project not indexed) so * the caller can climb to another candidate project root. */
| 451 | * the requested path. *is_error is set for MCP errors (project not indexed) so |
| 452 | * the caller can climb to another candidate project root. */ |
| 453 | static char *ha_coverage_context(const char *envelope, const char *rel, bool *is_error) { |
| 454 | *is_error = false; |
| 455 | yyjson_doc *edoc = yyjson_read(envelope, strlen(envelope), 0); |
| 456 | if (!edoc) { |
| 457 | return NULL; |
| 458 | } |
| 459 | yyjson_val *eroot = yyjson_doc_get_root(edoc); |
| 460 | yyjson_val *err = yyjson_obj_get(eroot, "isError"); |
| 461 | if (err && yyjson_is_true(err)) { |
| 462 | *is_error = true; |
| 463 | yyjson_doc_free(edoc); |
| 464 | return NULL; |
| 465 | } |
| 466 | yyjson_val *content = yyjson_obj_get(eroot, "content"); |
| 467 | yyjson_val *item0 = (content && yyjson_is_arr(content)) ? yyjson_arr_get(content, 0) : NULL; |
| 468 | const char *inner = ha_obj_str(item0, "text"); |
| 469 | if (!inner) { |
| 470 | yyjson_doc_free(edoc); |
| 471 | return NULL; |
| 472 | } |
| 473 | yyjson_doc *idoc = yyjson_read(inner, strlen(inner), 0); |
| 474 | if (!idoc) { |
| 475 | yyjson_doc_free(edoc); |
| 476 | return NULL; |
| 477 | } |
| 478 | yyjson_val *iroot = yyjson_doc_get_root(idoc); |
| 479 | char *text = NULL; |
| 480 | yyjson_val *paths = yyjson_obj_get(iroot, "paths"); |
| 481 | yyjson_val *item = paths && yyjson_is_arr(paths) ? yyjson_arr_get(paths, 0) : NULL; |
| 482 | const char *path = ha_obj_str(item, "path"); |
| 483 | const char *status = ha_obj_str(item, "status"); |
| 484 | const char *freshness = ha_obj_str(item, "freshness"); |
| 485 | const char *action = ha_obj_str(item, "recommended_action"); |
| 486 | if (item && (!path || strcmp(path, rel) == 0) && status && |
| 487 | strcmp(status, "no_recorded_issue") != 0 && strcmp(status, "outside_project") != 0 && |
| 488 | strcmp(status, "invalid_path") != 0) { |
| 489 | const char *kind = NULL; |
| 490 | const char *detail = NULL; |
| 491 | yyjson_val *coverage = yyjson_obj_get(item, "coverage"); |
| 492 | yyjson_val *row = coverage && yyjson_is_arr(coverage) ? yyjson_arr_get(coverage, 0) : NULL; |
| 493 | if (row) { |
| 494 | kind = ha_obj_str(row, "kind"); |
| 495 | detail = ha_obj_str(row, "detail"); |
| 496 | } |
| 497 | text = malloc(1536); |
| 498 | if (text) { |
| 499 | if (strcmp(status, "partial") == 0) { |
| 500 | snprintf(text, 1536, |
| 501 | "[codebase-memory] Coverage note: this file was only PARTIALLY indexed; " |
| 502 | "line range(s) %s may be missing from graph results. freshness=%s. The " |
| 503 | "source is ground truth; action=%s. (best-effort signal)", |
| 504 | detail && detail[0] ? detail : "?", freshness ? freshness : "unavailable", |
| 505 | action ? action : "read_file_and_verify_scope"); |
| 506 | } else if (strcmp(status, "skipped") == 0 || strcmp(status, "excluded") == 0) { |
| 507 | snprintf(text, 1536, |
| 508 | "[codebase-memory] Coverage note: this file is not reliably represented " |
| 509 | "in the graph (status=%s, kind=%s%s%s, freshness=%s). action=%s. " |
| 510 | "(best-effort signal)", |
no test coverage detected