Parse the MCP envelope returned by cbm_mcp_handle_tool and, if it is a * successful search_graph result with >=1 hit, format a compact * additionalContext string. Returns malloc'd text or NULL. * * *is_error is set when the envelope is an MCP error (e.g. project not * indexed) so the caller can try a parent directory. */
| 159 | * *is_error is set when the envelope is an MCP error (e.g. project not |
| 160 | * indexed) so the caller can try a parent directory. */ |
| 161 | static char *ha_format_context(const char *envelope, const char *token, bool *is_error) { |
| 162 | *is_error = false; |
| 163 | yyjson_doc *edoc = yyjson_read(envelope, strlen(envelope), 0); |
| 164 | if (!edoc) { |
| 165 | return NULL; |
| 166 | } |
| 167 | yyjson_val *eroot = yyjson_doc_get_root(edoc); |
| 168 | yyjson_val *err = yyjson_obj_get(eroot, "isError"); |
| 169 | if (err && yyjson_is_true(err)) { |
| 170 | *is_error = true; |
| 171 | yyjson_doc_free(edoc); |
| 172 | return NULL; |
| 173 | } |
| 174 | yyjson_val *content = yyjson_obj_get(eroot, "content"); |
| 175 | yyjson_val *item0 = (content && yyjson_is_arr(content)) ? yyjson_arr_get(content, 0) : NULL; |
| 176 | const char *inner = ha_obj_str(item0, "text"); |
| 177 | if (!inner) { |
| 178 | yyjson_doc_free(edoc); |
| 179 | return NULL; |
| 180 | } |
| 181 | |
| 182 | yyjson_doc *idoc = yyjson_read(inner, strlen(inner), 0); |
| 183 | if (!idoc) { |
| 184 | yyjson_doc_free(edoc); |
| 185 | return NULL; |
| 186 | } |
| 187 | yyjson_val *iroot = yyjson_doc_get_root(idoc); |
| 188 | yyjson_val *results = yyjson_obj_get(iroot, "results"); |
| 189 | size_t nres = (results && yyjson_is_arr(results)) ? yyjson_arr_size(results) : 0; |
| 190 | if (nres == 0) { |
| 191 | yyjson_doc_free(idoc); |
| 192 | yyjson_doc_free(edoc); |
| 193 | return NULL; /* valid project, just no matching symbols */ |
| 194 | } |
| 195 | |
| 196 | char *text = malloc(4096); |
| 197 | if (!text) { |
| 198 | yyjson_doc_free(idoc); |
| 199 | yyjson_doc_free(edoc); |
| 200 | return NULL; |
| 201 | } |
| 202 | int off = snprintf(text, 4096, |
| 203 | "[codebase-memory] %zu graph symbol(s) match \"%s\" " |
| 204 | "(structured context; your search results below are " |
| 205 | "unaffected):", |
| 206 | nres, token); |
| 207 | size_t idx; |
| 208 | size_t maxn; |
| 209 | yyjson_val *r; |
| 210 | yyjson_arr_foreach(results, idx, maxn, r) { |
| 211 | if (off < 0 || off >= 3900) { |
| 212 | break; |
| 213 | } |
| 214 | const char *qn = ha_obj_str(r, "qualified_name"); |
| 215 | const char *nm = ha_obj_str(r, "name"); |
| 216 | const char *fp = ha_obj_str(r, "file_path"); |
| 217 | const char *lb = ha_obj_str(r, "label"); |
| 218 | const char *disp = (qn && qn[0]) ? qn : (nm ? nm : ""); |
no test coverage detected