diff --git a/src/cli/hook_augment.c b/src/cli/hook_augment.c index 79a197716..ef6b7a3fe 100644 --- a/src/cli/hook_augment.c +++ b/src/cli/hook_augment.c @@ -377,6 +377,13 @@ static char *ha_build_args(const char *project, const char *token) { return out; /* caller frees */ } +/* Closes every search augmentation that found symbols: cbm's own statement, + * outside the untrusted data block, naming the graph tool for the likely next + * question at the moment the agent greps. Facts, not commands. */ +static const char HA_SEARCH_FOLLOW_UP[] = + "\n[codebase-memory] For these symbols, trace_path lists callers and callees, " + "get_code_snippet returns the exact source, and search_graph finds related symbols by name."; + /* 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. @@ -432,7 +439,7 @@ static char *ha_format_context(const char *envelope, const char *token, bool *is return NULL; /* valid project, just no matching symbols */ } - char *text = malloc(4096); + char *text = malloc(4096 + sizeof(HA_SEARCH_FOLLOW_UP)); if (!text) { yyjson_doc_free(idoc); yyjson_doc_free(edoc); @@ -476,6 +483,10 @@ static char *ha_format_context(const char *envelope, const char *token, bool *is safe_path, safe_label[0] ? " " : "", safe_label); } } + /* off can run past the list cap when a row is truncated, so append at the + * real end; the allocation reserves room for the whole follow-up line. */ + size_t used = strlen(text); + snprintf(text + used, 4096 + sizeof(HA_SEARCH_FOLLOW_UP) - used, "%s", HA_SEARCH_FOLLOW_UP); yyjson_doc_free(idoc); yyjson_doc_free(edoc); diff --git a/tests/test_cli.c b/tests/test_cli.c index c02091d78..539b0df18 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -13498,6 +13498,50 @@ TEST(cli_hook_augment_context_tracks_search_json_shape) { PASS(); } +/* A search that found symbols closes with cbm's own follow-up line, after the + * untrusted symbol list and on its own line, naming the graph tools for the + * next question; it carries no repository-derived value. */ +TEST(cli_hook_augment_context_names_graph_follow_ups) { + cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL); + ASSERT_NOT_NULL(srv); + cbm_store_t *st = cbm_mcp_server_store(srv); + const char *proj = "hookproj"; + cbm_mcp_server_set_project(srv, proj); + cbm_store_upsert_project(st, proj, "/tmp/hookproj"); + cbm_node_t n = {.project = proj, + .label = "Function", + .name = "someIndexedSymbol", + .qualified_name = "hookproj.mod.someIndexedSymbol", + .file_path = "mod.py", + .start_line = 1, + .end_line = 4}; + ASSERT_GT(cbm_store_upsert_node(st, &n), 0); + char *envelope = + cbm_mcp_handle_tool(srv, "search_graph", + "{\"project\":\"hookproj\",\"name_pattern\":\".*someIndexedSymbol.*\"," + "\"limit\":5,\"format\":\"json\"}"); + ASSERT_NOT_NULL(envelope); + + bool is_error = true; + char *ctx = + cbm_hook_augment_format_context_for_testing(envelope, "someIndexedSymbol", &is_error); + ASSERT_FALSE(is_error); + ASSERT_NOT_NULL(ctx); + const char *list = strstr(ctx, "\n- hookproj.mod.someIndexedSymbol"); + const char *follow = strstr(ctx, "\n[codebase-memory] For these symbols, trace_path lists " + "callers and callees, get_code_snippet returns the exact " + "source, and search_graph finds related symbols by name."); + ASSERT_NOT_NULL(list); + ASSERT_NOT_NULL(follow); + ASSERT_TRUE(follow > list); + ASSERT_NULL(strstr(follow, "someIndexedSymbol")); + ASSERT_NULL(strstr(follow, "mod.py")); + free(ctx); + free(envelope); + cbm_mcp_server_free(srv); + PASS(); +} + /* Exercise the real PreToolUse + Bash route instead of calling only the * tokenizer seam. This pins the event guard and the graph lookup path together. */ @@ -18479,6 +18523,7 @@ SUITE(cli) { RUN_TEST(cli_codex_preflight_reports_heading_and_reason); #endif RUN_TEST(cli_hook_augment_context_tracks_search_json_shape); + RUN_TEST(cli_hook_augment_context_names_graph_follow_ups); RUN_TEST(cli_hook_augment_bash_pretooluse_reaches_augmenter); RUN_TEST(cli_hook_augment_bash_pattern_extractor); RUN_TEST(cli_hook_augment_coverage_requests_machine_json);