From cc53da1dc7d36e9ba8400926ba8ba13646ccf082 Mon Sep 17 00:00:00 2001 From: HumbleTurtle Date: Fri, 21 Aug 2026 22:42:31 -0300 Subject: [PATCH] fix: align local and server graph depth semantics --- resources/scripts/napseer_mcp_server.py | 12 +++++- tests/test_mcp_memory_surface_efficiency.py | 42 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/resources/scripts/napseer_mcp_server.py b/resources/scripts/napseer_mcp_server.py index 0b6c473..e372c95 100755 --- a/resources/scripts/napseer_mcp_server.py +++ b/resources/scripts/napseer_mcp_server.py @@ -11119,6 +11119,8 @@ def local_memory_context(args, seed_ids): nodes[current_id] = compact level_ids.append(current_id) indexed_at = max(indexed_at, str(current["indexed_at"] or "")) + if current_depth >= depth: + continue candidates = [] if direction in {"outgoing", "both"}: candidates.extend(conn.execute( @@ -11130,18 +11132,24 @@ def local_memory_context(args, seed_ids): "SELECT * FROM local_index_edges WHERE project_id = ? AND (target_node_id = ? OR target_path = ?) ORDER BY relation, source_node_id", (project_id, current_id, current["full_path"]), ).fetchall()) + resolved_candidates = [] for edge in candidates: if relations and edge["relation"] not in relations: continue target = node_row(node_id=edge["target_node_id"]) if edge["target_node_id"] else node_row(path=edge["target_path"]) if target is None: continue + resolved_candidates.append((edge, target)) + resolved_candidates.sort(key=lambda item: ( + item[0]["source_node_id"], + item[1]["node_id"], + item[0]["relation"], + )) + for edge, target in resolved_candidates: source_id = edge["source_node_id"] target_id = target["node_id"] neighbor_id = target_id if source_id == current_id else source_id known_neighbor = neighbor_id in seen or neighbor_id in next_ids - if current_depth >= depth and not known_neighbor: - continue if not known_neighbor: if len(nodes) + len(next_ids) >= max_nodes: frontier.append(neighbor_id) diff --git a/tests/test_mcp_memory_surface_efficiency.py b/tests/test_mcp_memory_surface_efficiency.py index ad9d0a0..baa4aaf 100644 --- a/tests/test_mcp_memory_surface_efficiency.py +++ b/tests/test_mcp_memory_surface_efficiency.py @@ -150,6 +150,48 @@ def test_hybrid_context_falls_back_until_local_graph_coverage_is_complete(mod): assert calls == [["root"]] +def test_local_context_does_not_emit_edges_beyond_requested_depth(mod): + state = tempfile.TemporaryDirectory() + mod._surface_depth_state = state + mod.AUTH_DIR = pathlib.Path(state.name) + mod.INDEX_PATH = mod.AUTH_DIR / "index.sqlite" + mod.INDEX_LOCK_PATH = mod.AUTH_DIR / "index.sqlite.lock" + nodes = [ + { + "id": "root", "project_id": "project-1", "full_path": "/docs/root", + "folder_path": "/docs", "name": "root", "type": "note", "metadata": {}, + "tags": [], "links": [ + {"node_id": "left", "relation": "references"}, + {"node_id": "right", "relation": "references"}, + ], "content_text": "", "updated_at": "2026-08-16T12:00:00Z", + }, + { + "id": "left", "project_id": "project-1", "full_path": "/docs/left", + "folder_path": "/docs", "name": "left", "type": "note", "metadata": {}, + "tags": [], "links": [{"node_id": "right", "relation": "explains"}], + "content_text": "", "updated_at": "2026-08-16T12:01:00Z", + }, + { + "id": "right", "project_id": "project-1", "full_path": "/docs/right", + "folder_path": "/docs", "name": "right", "type": "note", "metadata": {}, + "tags": [], "links": [], "content_text": "", + "updated_at": "2026-08-16T12:02:00Z", + }, + ] + for item in nodes: + mod.index_node(item) + + result = mod.get_memory_context({ + "node_id": "root", "mode": "local", "depth": 1, "direction": "both", + }) + + assert {item["id"] for item in result["nodes"]} == {"root", "left", "right"} + assert result["edges"] == [ + {"source": "root", "target": "left", "relation": "references"}, + {"source": "root", "target": "right", "relation": "references"}, + ] + + def test_index_sync_upgrades_pre_graph_index_with_full_reindex(mod): state = tempfile.TemporaryDirectory() mod._surface_upgrade_state = state