diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index 65dc262a4..fba98f68a 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -4278,6 +4278,14 @@ static char *handle_get_architecture(cbm_mcp_server_t *srv, const char *args) { add_stale_derived_view_warning(doc, root, CBM_STORE_DERIVED_VIEW_ROUTES, "routes derived view is stale; route results may be stale."); } + int dirty_pending = 0; + int dirty_overlay_ready = 0; + if (get_dirty_file_counts(store, project, &dirty_pending, &dirty_overlay_ready)) { + add_dirty_file_freshness_counts(doc, root, dirty_pending, dirty_overlay_ready); + add_response_warning(doc, root, + "get_architecture reads canonical graph summaries; dirty file " + "changes may be absent until overlay or reindex completes."); + } /* Node label summary */ if (aspect_wanted(aspects_doc, aspects_arr, "structure")) { diff --git a/tests/test_mcp.c b/tests/test_mcp.c index f2c578916..a4f4956e3 100644 --- a/tests/test_mcp.c +++ b/tests/test_mcp.c @@ -1767,6 +1767,51 @@ TEST(tool_get_architecture_warns_on_stale_derived_views) { PASS(); } +TEST(tool_get_architecture_reports_dirty_metadata_as_canonical_only) { + cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL); + ASSERT_NOT_NULL(srv); + cbm_store_t *st = cbm_mcp_server_store(srv); + ASSERT_NOT_NULL(st); + + const char *proj = "arch-dirty"; + ASSERT_EQ(cbm_store_upsert_project(st, proj, "/tmp/arch-dirty"), CBM_STORE_OK); + cbm_mcp_server_set_project(srv, proj); + + cbm_node_t fn = {.project = proj, + .label = "Function", + .name = "Run", + .qualified_name = "arch-dirty.Run", + .file_path = "run.c", + .properties_json = "{\"is_entry_point\":true}"}; + ASSERT_GT(cbm_store_upsert_node(st, &fn), 0); + + cbm_dirty_file_state_t dirty = {.project = proj, + .rel_path = "run.c", + .observed_hash = "arch-dirty-hash", + .observed_generation = 13, + .source = CBM_STORE_DIRTY_SOURCE_EXPLICIT_REINDEX, + .status = CBM_STORE_DIRTY_STATUS_PENDING}; + ASSERT_EQ(cbm_store_upsert_dirty_file(st, &dirty), CBM_STORE_OK); + + char *resp = cbm_mcp_server_handle( + srv, "{\"jsonrpc\":\"2.0\",\"id\":95,\"method\":\"tools/call\"," + "\"params\":{\"name\":\"get_architecture\"," + "\"arguments\":{\"project\":\"arch-dirty\",\"aspects\":[\"all\"]}}}"); + ASSERT_NOT_NULL(resp); + char *inner = extract_text_content(resp); + ASSERT_NOT_NULL(inner); + ASSERT_NOT_NULL(strstr(inner, "\"entry_points\"")); + ASSERT_NOT_NULL(strstr(inner, "Run")); + ASSERT_NOT_NULL(strstr(inner, "\"warnings\"")); + ASSERT_NOT_NULL(strstr(inner, "get_architecture reads canonical graph summaries")); + ASSERT_TRUE(has_dirty_freshness_counts(inner, 1, 0)); + + free(inner); + free(resp); + cbm_mcp_server_free(srv); + PASS(); +} + TEST(tool_get_architecture_path_scoping) { cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL); ASSERT_NOT_NULL(srv); @@ -3995,6 +4040,7 @@ SUITE(mcp) { RUN_TEST(tool_get_architecture_empty); RUN_TEST(tool_get_architecture_emits_populated_sections); RUN_TEST(tool_get_architecture_warns_on_stale_derived_views); + RUN_TEST(tool_get_architecture_reports_dirty_metadata_as_canonical_only); RUN_TEST(tool_get_architecture_path_scoping); RUN_TEST(tool_query_graph_missing_query);