feat(serve): add multi-graph support to MCP server (#581) - #2099
feat(serve): add multi-graph support to MCP server (#581)#2099YanisGuerault wants to merge 4 commits into
Conversation
12f436c to
6d020bd
Compare
6d020bd to
2e39ab0
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds a multi-graph MCP server mode alongside the existing single-graph one: a new GraphRegistry loads either a single graph.json or every <name>/graph.json under a directory, rescans on an interval to pick up file changes (dropping graphs whose files vanish or fail to load), and _resolve_graph selects a target by explicit graph param, session default, or the sole loaded graph. Reworks _build_server to route all tool handlers through the registry and expose list_graphs/use_graph, and returns its handler dict for testing. Ships a multi Docker target driven by GRAPHS_DIR/SCAN_INTERVAL/PORT env vars plus a docker-compose.multi.yml and README docs, leaving the default single-graph image and entrypoint unchanged.
Worth a look
- Default Docker build now produces multi-graph image instead of existing single-graph image —
Dockerfile:28· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Multi-graph compose publishes unauthenticated MCP service by default —
docker-compose.multi.yml:11· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- except (OSError, SystemExit, Exception) swallows SystemExit and all errors, deleting graph —
graphify/serve.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- GraphRegistry.get/names read shared _graphs without lock —
graphify/serve.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Non-atomic single-graph resolution can return None during concurrent rescan —
graphify/serve.py:146· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1850 functions depend on the 1647 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 122 callees - new:
_build_server()— 12 callers, 17 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_score_query()— 15 callers, 6 callees - new:
_build_http_app()— 19 callers, 4 callees - new:
_query_terms()— 20 callers, 3 callees - new:
_subgraph_to_text()— 20 callers, 3 callees - new:
_main()— 8 callers, 7 callees - …and 14 more — each is listed as a finding
Verification — 1850 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1689 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_build\_http\_app.
The verifier did not have enough to check \_build\_http\_app, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 24 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 54 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Verification did not run: Verification did not run for \_main.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-a1e2a6c2baa84ce596d72365c42bb445/head/a' error: Graph path
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Verification did not run: Verification did not run for serve.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-a1e2a6c2baa84ce596d72365c42bb445/head/a longer string' erro
Verification did not run: Verification did not run for serve\_http.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=124): timeout after 30s
· 9 grounded finding(s) anchored inline below; 13 more finding(s) on lines outside this diff (see the check run).
| ) | ||
|
|
||
|
|
||
| def _load_graph(graph_path: str) -> nx.Graph: |
There was a problem hiding this comment.
_load_graph()
12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def _build_server(graph_path: str): | ||
| def _build_server(registry: GraphRegistry, *, session_state: dict | None = None): |
There was a problem hiding this comment.
_build_server()
fans out to 17 callees (efferent coupling); 12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| path = _resolve_graph_path(project_path) | ||
| G, communities = _load_ctx(path) | ||
| active_graph_path = str(Path(path).resolve()) | ||
| def _get_ctx(arguments: dict) -> GraphContext: |
There was a problem hiding this comment.
_get_ctx()
9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1781,6 +1919,8 @@ def _tool_get_node(arguments: dict) -> str: | |||
| ]) | |||
|
|
|||
| def _tool_get_neighbors(arguments: dict) -> str: | |||
There was a problem hiding this comment.
_tool_get_neighbors()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1884,6 +2035,8 @@ def _tool_list_prs(arguments: dict) -> str: | |||
| return format_prs_text(prs, base) | |||
|
|
|||
| def _tool_get_pr_impact(arguments: dict) -> str: | |||
There was a problem hiding this comment.
_tool_get_pr_impact()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1990,17 +2174,33 @@ async def list_resources() -> list[types.Resource]: | |||
| ] | |||
|
|
|||
| async def read_resource(uri: AnyUrl) -> str: | |||
There was a problem hiding this comment.
read_resource()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return server, _handlers | ||
|
|
||
|
|
||
| def serve(graph_path: str | None = None) -> None: |
There was a problem hiding this comment.
serve()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2175,8 +2374,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
19 callers depend on it (afferent 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.
3e02e0a to
70194e1
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. 2 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds a public graphify <repo>... --mcp CLI path that serves one or more existing repository graphs over MCP, parsed by _run_mcp_cli and dispatched to stdio via serve or HTTP via serve_http, rejecting duplicate repo basenames, missing graphify-out/graph.json, and malformed transport/host/port/api-key options. Introduces GraphRegistry and GraphContext to hold multiple named graphs, with from_paths skipping the learning overlay and project-path access, and exposes list_graphs/use_graph tools plus an optional per-tool graph parameter so callers can target a specific graph or set a session default. Retools the Dockerfile and adds docker-compose.multi.yml to run the public CLI against a mounted repo tree instead of baking a graph into the image.
Worth a look
- HTTP transport binds to 0.0.0.0 without requiring API key —
graphify/__main__.py:519· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- CLI allows unauthenticated non-loopback HTTP MCP binding —
graphify/__main__.py:584· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- GraphRegistry.get/names read shared dict without holding the lock while rescan mutates it —
graphify/serve.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Per-call project_path graph selection removed from tool dispatch —
graphify/serve.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- MCP tool calls no longer honor project_path routing —
graphify/serve.py:2080· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 839 functions depend on the 570 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 122 callees - new:
_build_server()— 14 callers, 17 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_score_query()— 15 callers, 6 callees - new:
_build_http_app()— 20 callers, 4 callees - new:
render()— 13 callers, 5 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - …and 21 more — each is listed as a finding
Verification — 839 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 827 function(s) in the blast radius were not formally verified this run
Formal verification
No difference found (not proven): No behavior difference found in \_run\_cli (not a proof).
The verifier ran both versions of \_run\_cli on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_build\_http\_app.
The verifier did not have enough to check \_build\_http\_app, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 24 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 54 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_load\_graph.
The verifier did not have enough to check \_load\_graph, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Verification did not run: Verification did not run for \_main.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-7af6d2a6766b4d54bd60987fc9155ca7/head/a' error: Graph path
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Verification did not run: Verification did not run for serve.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-7af6d2a6766b4d54bd60987fc9155ca7/head/a longer string' erro
Verification did not run: Verification did not run for serve\_http.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=124): timeout after 30s
No difference found (not proven): No behavior difference found in monolith\_roundtrip (not a proof).
The verifier ran both versions of monolith\_roundtrip on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 11 grounded finding(s) anchored inline below; 18 more finding(s) on lines outside this diff (see the check run).
| serve(registry=registry) | ||
|
|
||
|
|
||
| def _serve_mcp_repositories( |
There was a problem hiding this comment.
_serve_mcp_repositories()
6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def _run_cli() -> None: |
There was a problem hiding this comment.
_run_cli()
high coupling complexity (Ca·Ce = 15).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ) | ||
|
|
||
|
|
||
| def _load_graph(graph_path: str, *, load_learning_overlay: bool = True) -> nx.Graph: |
There was a problem hiding this comment.
_load_graph()
12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def _build_server(graph_path: str): | ||
| def _build_server(registry: GraphRegistry, *, session_state: dict | None = None): |
There was a problem hiding this comment.
_build_server()
fans out to 17 callees (efferent coupling); 14 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return fallback_session_state | ||
| return session_states.setdefault(session, {}) | ||
|
|
||
| def _get_ctx(arguments: dict) -> GraphContext: |
There was a problem hiding this comment.
_get_ctx()
fans out to 7 callees (efferent coupling); 9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1884,6 +2023,8 @@ def _tool_list_prs(arguments: dict) -> str: | |||
| return format_prs_text(prs, base) | |||
|
|
|||
| def _tool_get_pr_impact(arguments: dict) -> str: | |||
There was a problem hiding this comment.
_tool_get_pr_impact()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1990,17 +2164,34 @@ async def list_resources() -> list[types.Resource]: | |||
| ] | |||
|
|
|||
| async def read_resource(uri: AnyUrl) -> str: | |||
There was a problem hiding this comment.
read_resource()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def serve(graph_path: str | None = None) -> None: | ||
| def serve(graph_path: str | None = None, *, registry: GraphRegistry | None = None) -> None: |
There was a problem hiding this comment.
serve()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2175,8 +2366,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
20 callers depend on it (afferent 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.
9d2e6ac to
91c928e
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. 2 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds a multi-graph MCP mode: graphify <repo>... --mcp serves the existing graphify-out/graph.json of one or more repositories over stdio or HTTP, exposing the usual tools plus list_graphs/use_graph and an optional per-tool graph selector with a session default. Introduces GraphRegistry/GraphContext to hold named graphs (basenames must be unique), lazily build trigram/community data, and rescan reloads only graphs whose graph.json mtime changed. HTTP binds still go through _validate_http_bind, so remote (--host 0.0.0.0) requires a nonblank API key, and the Docker image now defaults to graphify /data --mcp --transport http with the repo mounted read-only.
Worth a look
- call_tool dropped per-call graph selection via project_path —
graphify/serve.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Removal of per-request _select_graph makes multi-graph tool dispatch share mutable global state —
graphify/serve.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Duplicate repository graph names are silently overwritten —
graphify/serve.py:56· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- from_paths uses parent.parent.name causing name collisions/empty names —
graphify/serve.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- rescan() except clause deletes graph on any exception, not just file errors —
graphify/serve.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 671 functions depend on the 466 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 122 callees - new:
_build_server()— 14 callers, 17 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_score_query()— 15 callers, 6 callees - new:
_build_http_app()— 20 callers, 4 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - new:
_subgraph_to_text()— 20 callers, 3 callees - …and 17 more — each is listed as a finding
Verification — 671 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 659 function(s) in the blast radius were not formally verified this run
Formal verification
No difference found (not proven): No behavior difference found in \_run\_cli (not a proof).
The verifier ran both versions of \_run\_cli on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify \_infer\_merge\_root.
The verifier did not have enough to check \_infer\_merge\_root, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `graph_path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify prefix\_graph\_for\_global.
The verifier did not have enough to check prefix\_graph\_for\_global, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 115 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous
Could not verify: Could not verify dispatch\_command.
The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_is\_ignored.
The verifier did not have enough to check \_is\_ignored, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_match\_anchored\_ignore\_pattern (not a proof).
The verifier ran both versions of \_match\_anchored\_ignore\_pattern on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify detect.
The verifier did not have enough to check detect, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_csharp\_extra\_walk.
The verifier did not have enough to check \_csharp\_extra\_walk, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_js\_extra\_walk.
The verifier did not have enough to check \_js\_extra\_walk, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_ts\_extra\_walk.
The verifier did not have enough to check \_ts\_extra\_walk, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify introspect\_postgres.
The verifier did not have enough to check introspect\_postgres, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 24 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly ProgrammingError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_resolve\_source\_path.
The verifier did not have enough to check \_resolve\_source\_path, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `graph_path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify resolve\_ruby\_member\_calls.
The verifier did not have enough to check resolve\_ruby\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_build\_http\_app.
The verifier did not have enough to check \_build\_http\_app, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 24 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 54 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_load\_graph.
The verifier did not have enough to check \_load\_graph, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Verification did not run: Verification did not run for \_main.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-590480ab84794e8caddd656cbf949421/head/a' error: Graph path
Verification did not run: Verification did not run for serve.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-590480ab84794e8caddd656cbf949421/head/a longer string' erro
Verification did not run: Verification did not run for serve\_http.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=124): timeout after 30s
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)
Could not verify: Could not verify on\_any\_event.
The verifier did not have enough to check on\_any\_event, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)
Could not verify: Could not verify watch.
The verifier did not have enough to check watch, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set
· 12 grounded finding(s) anchored inline below; 13 more finding(s) on lines outside this diff (see the check run).
| serve(registry=registry) | ||
|
|
||
|
|
||
| def _serve_mcp_repositories( |
There was a problem hiding this comment.
_serve_mcp_repositories()
6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def _run_cli() -> None: |
There was a problem hiding this comment.
_run_cli()
high coupling complexity (Ca·Ce = 15).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ) | ||
|
|
||
|
|
||
| def _load_graph(graph_path: str, *, load_learning_overlay: bool = True) -> nx.Graph: |
There was a problem hiding this comment.
_load_graph()
12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def _build_server(graph_path: str): | ||
| def _build_server(registry: GraphRegistry, *, session_state: dict | None = None): |
There was a problem hiding this comment.
_build_server()
fans out to 17 callees (efferent coupling); 14 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return fallback_session_state | ||
| return session_states.setdefault(session, {}) | ||
|
|
||
| def _get_ctx(arguments: dict) -> GraphContext: |
There was a problem hiding this comment.
_get_ctx()
fans out to 7 callees (efferent coupling); 9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1990,17 +2166,34 @@ async def list_resources() -> list[types.Resource]: | |||
| ] | |||
|
|
|||
| async def read_resource(uri: AnyUrl) -> str: | |||
There was a problem hiding this comment.
read_resource()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def serve(graph_path: str | None = None) -> None: | ||
| def serve(graph_path: str | None = None, *, registry: GraphRegistry | None = None) -> None: |
There was a problem hiding this comment.
serve()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2175,8 +2368,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
20 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| raise ValueError("HTTP binding outside loopback requires --api-key") | ||
|
|
||
|
|
||
| def serve_http( |
There was a problem hiding this comment.
serve_http()
high coupling complexity (Ca·Ce = 12).
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.
Enable serving multiple knowledge graphs from a single MCP endpoint. Always registry-based: single graph = 1-entry registry, multi-graph = directory scan via --graphs-dir flag or GRAPHS_DIR env var. - Add GraphContext dataclass + GraphRegistry (from_path, from_directory) - Refactor _build_server to accept GraphRegistry with per-call resolution - Tool visibility keyed on registry size: list_graphs/use_graph when >1, PR tools when ==1. New graph param on all tool schemas. - Add --graphs-dir CLI flag forcing HTTP transport with auto-rescan - Remove multi_serve.py and graphify-multi-mcp entry point (consolidated) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace directory discovery with explicit repository paths and remove the legacy multi-MCP deployment workflow.
Reject unauthenticated non-loopback HTTP listeners and honor GRAPHIFY_API_KEY in the public MCP CLI.
91c928e to
2906f3f
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds a multi-graph MCP mode: graphify <repo>... --mcp resolves each repo's graphify-out/graph.json, builds a GraphRegistry, and serves them over stdio (default) or HTTP with --transport/--host/--port/--api-key, rejecting duplicate repo basenames and enforcing the HTTP bind/API-key validation. Each MCP tool gains an optional graph parameter to target a specific graph, backed by new list_graphs and use_graph tools and a session default, while registry-loaded graphs skip project-path resolution and the learning overlay and refresh on mtime change via rescan. Repoints the Dockerfile to the graphify entrypoint serving a mounted /data repo over HTTP and documents the multi-graph server in the README.
Worth a look
- from_paths overwrites graphs sharing the same grandparent directory name —
graphify/serve.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- use_graph tool omits 'graph' from its input schema but requires it —
graphify/serve.py· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Multi-graph MCP server drops PR tool handlers —
graphify/serve.py:2134· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Docker default command fails unauthenticated HTTP bind validation —
Dockerfile:15· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Invalid graph parse errors are not converted to CLI exits —
graphify/__main__.py:538· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 671 functions depend on the 466 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 122 callees - new:
_build_server()— 14 callers, 17 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_score_query()— 15 callers, 6 callees - new:
_build_http_app()— 20 callers, 4 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - new:
_subgraph_to_text()— 20 callers, 3 callees - …and 17 more — each is listed as a finding
Verification — 671 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 659 function(s) in the blast radius were not formally verified this run
Formal verification
No difference found (not proven): No behavior difference found in \_run\_cli (not a proof).
The verifier ran both versions of \_run\_cli on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_build\_http\_app.
The verifier did not have enough to check \_build\_http\_app, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 24 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 54 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_load\_graph.
The verifier did not have enough to check \_load\_graph, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Verification did not run: Verification did not run for \_main.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-6d70e3f451d34b69b47183ef1d4c0ad5/head/a' error: Graph path
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Verification did not run: Verification did not run for serve.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-6d70e3f451d34b69b47183ef1d4c0ad5/head/a longer string' erro
Verification did not run: Verification did not run for serve\_http.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=124): timeout after 30s
· 12 grounded finding(s) anchored inline below; 13 more finding(s) on lines outside this diff (see the check run).
| serve(registry=registry) | ||
|
|
||
|
|
||
| def _serve_mcp_repositories( |
There was a problem hiding this comment.
_serve_mcp_repositories()
6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def _run_cli() -> None: |
There was a problem hiding this comment.
_run_cli()
high coupling complexity (Ca·Ce = 15).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ) | ||
|
|
||
|
|
||
| def _load_graph(graph_path: str, *, load_learning_overlay: bool = True) -> nx.Graph: |
There was a problem hiding this comment.
_load_graph()
12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def _build_server(graph_path: str): | ||
| def _build_server(registry: GraphRegistry, *, session_state: dict | None = None): |
There was a problem hiding this comment.
_build_server()
fans out to 17 callees (efferent coupling); 14 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return fallback_session_state | ||
| return session_states.setdefault(session, {}) | ||
|
|
||
| def _get_ctx(arguments: dict) -> GraphContext: |
There was a problem hiding this comment.
_get_ctx()
fans out to 7 callees (efferent coupling); 9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1990,17 +2166,34 @@ async def list_resources() -> list[types.Resource]: | |||
| ] | |||
|
|
|||
| async def read_resource(uri: AnyUrl) -> str: | |||
There was a problem hiding this comment.
read_resource()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def serve(graph_path: str | None = None) -> None: | ||
| def serve(graph_path: str | None = None, *, registry: GraphRegistry | None = None) -> None: |
There was a problem hiding this comment.
serve()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2175,8 +2368,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
20 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| raise ValueError("HTTP binding outside loopback requires --api-key") | ||
|
|
||
|
|
||
| def serve_http( |
There was a problem hiding this comment.
serve_http()
high coupling complexity (Ca·Ce = 12).
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.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
Adds a multi-graph MCP server mode: graphify <repo>... --mcp serves one or more repositories' existing graphify-out/graph.json files over stdio or HTTP, exposing the usual tools plus list_graphs/use_graph and an optional per-call graph parameter, with a session-scoped default graph. Introduces GraphRegistry and GraphContext to hold each named graph, its communities, and mtime, with rescan reloading only changed graphs before requests and rejecting duplicate repository basenames. Enforces a nonblank API key for remote HTTP binds via _validate_http_bind, and repoints the Dockerfile to graphify /data --mcp mounting the repo read-only instead of running graphify.serve on a baked-in graph path.
Worth a look
- Single default graph is named after .graphify directory —
graphify/serve.py:52· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Duplicate multi-graph repository names are silently overwritten —
graphify/serve.py:74· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- except clause with (OSError, SystemExit, Exception) swallows SystemExit —
graphify/serve.py:87· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Transient reload failure permanently unregisters graph —
graphify/serve.py:96· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Failed hot reload permanently removes graph —
graphify/serve.py:99· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 672 functions depend on the 467 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_build_server()— 15 callers, 17 callees - new:
dispatch_command()— 2 callers, 122 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_score_query()— 15 callers, 6 callees - new:
_build_http_app()— 20 callers, 4 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - new:
_subgraph_to_text()— 20 callers, 3 callees - …and 17 more — each is listed as a finding
Verification — 672 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 660 function(s) in the blast radius were not formally verified this run
Formal verification
No difference found (not proven): No behavior difference found in \_run\_cli (not a proof).
The verifier ran both versions of \_run\_cli on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_build\_http\_app.
The verifier did not have enough to check \_build\_http\_app, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 24 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `graph_path_or_registry` is annotated `str | GraphRegistry` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Could not verify: Could not verify \_load\_graph.
The verifier did not have enough to check \_load\_graph, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Verification did not run: Verification did not run for \_main.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-08723558d04f4c899ffe1ebd611f275a/head/a' error: Graph path
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: signature changed: no shared positional arity
Verification did not run: Verification did not run for serve.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=1): error: Graph path must be a .json file, got: '/workspace/job-08723558d04f4c899ffe1ebd611f275a/head/a longer string' erro
Verification did not run: Verification did not run for serve\_http.
The verification could not execute (an environment/toolchain issue, not a statement about the code).
Guarantee: No guarantee, the check itself did not complete.
Note: Detail: harness produced no verdict (rc=124): timeout after 30s
· 12 grounded finding(s) anchored inline below; 13 more finding(s) on lines outside this diff (see the check run).
| serve(registry=registry) | ||
|
|
||
|
|
||
| def _serve_mcp_repositories( |
There was a problem hiding this comment.
_serve_mcp_repositories()
6 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def _run_cli() -> None: |
There was a problem hiding this comment.
_run_cli()
high coupling complexity (Ca·Ce = 15).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ) | ||
|
|
||
|
|
||
| def _load_graph(graph_path: str, *, load_learning_overlay: bool = True) -> nx.Graph: |
There was a problem hiding this comment.
_load_graph()
12 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def _build_server(graph_path: str): | ||
| def _build_server( |
There was a problem hiding this comment.
_build_server()
fans out to 17 callees (efferent coupling); 15 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return fallback_session_state | ||
| return session_states.setdefault(session, {}) | ||
|
|
||
| def _get_ctx(arguments: dict) -> GraphContext: |
There was a problem hiding this comment.
_get_ctx()
fans out to 7 callees (efferent coupling); 9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1990,17 +2175,34 @@ async def list_resources() -> list[types.Resource]: | |||
| ] | |||
|
|
|||
| async def read_resource(uri: AnyUrl) -> str: | |||
There was a problem hiding this comment.
read_resource()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
|
||
|
|
||
| def serve(graph_path: str | None = None) -> None: | ||
| def serve(graph_path: str | None = None, *, registry: GraphRegistry | None = None) -> None: |
There was a problem hiding this comment.
serve()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2175,8 +2377,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
20 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| raise ValueError("HTTP binding outside loopback requires --api-key") | ||
|
|
||
|
|
||
| def serve_http( |
There was a problem hiding this comment.
serve_http()
high coupling complexity (Ca·Ce = 12).
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.
Summary
--graphs-dirflag orGRAPHS_DIRenv varChanges
GraphContextdataclass +GraphRegistry(from_path,from_directory, hot-reload viarescan)_build_serverto acceptGraphRegistrywith per-call context resolution (_get_ctx)list_graphs/use_graphwhen >1, PR tools when ==1graphparam on all tool schemas (replacesproject_path)--graphs-dirCLI flag forces HTTP transport with daemon rescan thread🤖 Generated with Claude Code