fix(mcp): report dirty freshness for architecture

Add canonical-only dirty freshness metadata to get_architecture responses so architecture summaries stay visible while pending dirty files are disclosed.

Cover the behavior with an MCP regression test and validate with focused architecture canary, full MCP suite, source-safety, diff hygiene, and product build logs under /private/tmp.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
Andrew Hundt
2026-07-03 16:54:18 -04:00
parent 509ce5352d
commit 56c8e10bc5
2 changed files with 54 additions and 0 deletions
+8
View File
@@ -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")) {
+46
View File
@@ -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);