fix(serve): bound multi-project graph contexts - #2269
Conversation
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR adds a bounded LRU cache for graph contexts in the MCP server (graphify/serve.py). It introduces a _GraphContextCache class and a _max_server_contexts() helper driven by a new GRAPHIFY_MAX_CONTEXTS environment variable (default 8, minimum 1), replacing the previous unbounded per-graph context dict while keeping the server's configured default graph pinned separately from the project-graph LRU. Supporting changes include moving os/threading imports to module scope, resolving graph paths consistently via Path.resolve(), a new README table entry documenting the env var, and test additions in tests/test_serve_http.py covering env-var parsing and LRU/pinning behavior (including a parametrized helper to create multiple named projects). The surface area touches the server build/load path, the context caching mechanism, documentation, and HTTP serve tests.
No blocking issues surfaced. 7 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
graphify review
Impact — 415 function(s) in the blast radius of 162 changed node(s).
Health — regressions introduced by this change:
- worsened (Ca·Ce 34→38):
_build_http_app()(Ca=19 Ce=2) — 19 callers depend on it (afferent coupling) - worsened (Ca·Ce 28→32):
_build_server()(Ca=2 Ce=16) — fans out to 16 callees (efferent coupling) - new offender:
test_corrupt_project_graph_is_a_tool_error_without_killing_server()(Ca=0 Ce=7) — fans out to 7 callees (efferent coupling) - new offender:
test_project_context_cache_is_lru_and_pins_default_graph()(Ca=0 Ce=7) — fans out to 7 callees (efferent coupling)
Verification — 415 function(s) need re-proving (callee-first):
graphify_serve_trigrams → tests_test_serve_test_trigrams_basic → graphify_serve_strip_diacritics → graphify_serve_search_tokens → graphify_serve_node_search_text → graphify_serve_get_trigram_index → tests_test_serve_test_trigram_index_cached_and_rebuilt_per_graph → graphify_serve_trigram_candidates → tests_test_serve_test_trigram_candidates_fast_path_fires_for_rare_term → tests_test_serve_test_trigram_candidates_falls_back_on_short_token …
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not enforced this run (re-run with --prove to verify the blast radius)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 285 function(s) in blast radius — re-run with
--proveto formally verify them
· 2 grounded finding(s) anchored inline below; 2 more finding(s) on lines outside this diff (see the check run).
| assert serve_mod._max_server_contexts() == expected | ||
|
|
||
|
|
||
| def test_project_context_cache_is_lru_and_pins_default_graph(tmp_path, monkeypatch): |
There was a problem hiding this comment.
test_project_context_cache_is_lru_and_pins_default_graph()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| assert "Nodes: 2" in _call_tool(client, headers, "graph_stats", {}, rid=3) | ||
|
|
||
|
|
||
| def test_corrupt_project_graph_is_a_tool_error_without_killing_server(tmp_path): |
There was a problem hiding this comment.
test_corrupt_project_graph_is_a_tool_error_without_killing_server()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Closes #2268
Summary
GRAPHIFY_MAX_CONTEXTSand add cache/LRU/corrupt-graph regression coverage.Testing
/private/tmp/graphify-memory-audit/bin/pytest -q tests/test_serve_http.py— 27 passedpython -m compileall -q graphify/serve.pygit diff --check