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 2 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.
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.
fe29dd7 to
ac3b845
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 server mode driven by graphify <repo>... --mcp, resolving each repo's graphify-out/graph.json, rejecting duplicate basenames and missing graphs, and serving over stdio or HTTP (with HTTP bind validation requiring a nonblank API key for remote hosts) via a new GraphRegistry that loads and caches each graph without the learning overlay. Exposes list_graphs and use_graph tools and threads an optional graph parameter through every existing tool so callers can target a specific graph or set a session default. Rewrites the Dockerfile to use the graphify entrypoint mounting a repo at /data instead of baking a graph.json path, and documents the multi-graph setup in the README.
Worth a look
- GraphRegistry.rescan mutates dict while iterating and deletes on any Exception —
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
- Session state 'current_graph' shared across concurrent MCP clients without isolation —
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
- Docker default command violates HTTP auth contract —
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
- from_paths uses grandparent dir name for graph key while from_path uses parent, risking key collisions/mismatch —
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() exception handler catches SystemExit and swallows all errors, deleting graph on transient stat failure —
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 — 704 functions depend on the 482 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_build_server()— 15 callers, 17 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_build_http_app()— 23 callers, 4 callees - new:
_score_query()— 15 callers, 6 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - new:
_subgraph_to_text()— 20 callers, 3 callees - …and 18 more — each is listed as a finding
Verification — 704 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: 692 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-bf00da44b5554b70b72e3885ddb42fad/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
No difference found (not proven): No behavior difference found in \_resolve\_single\_node (not a proof).
The verifier ran both versions of \_resolve\_single\_node 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.
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-bf00da44b5554b70b72e3885ddb42fad/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
· 13 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.
| @@ -2013,17 +2195,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.
| @@ -2209,8 +2405,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
23 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.
ac3b845 to
f1dc8c0
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 serving mode: graphify <repo>... --mcp (and --graphs-dir for recursive discovery via _discover_mcp_graphs) resolves each repo's graphify-out/graph.json, builds a GraphRegistry, and serves over stdio or HTTP, exposing list_graphs/use_graph plus a per-tool graph argument. HTTP binds enforce a nonblank API key on non-loopback hosts, duplicate repo basenames are rejected, and symlinked/hidden directories are skipped during discovery. Reworks the Dockerfile to use the graphify entrypoint against a mounted /data repo, adds docker-compose.multi.yml for two read-only repo graphs, and documents the multi-graph and Compose workflows.
Worth a look
- _build_http_app default graph_path=None can call Path(None) —
graphify/serve.py:2468· 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 image defaults to unauthenticated HTTP on all interfaces —
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
- from_path uses parent.name while from_paths uses parent.parent.name for the same graph.json layout —
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
- MCP project_path allows clients to load arbitrary filesystem graphs —
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
- project_path branch computes graph.json path but does not honor session current_graph or validate existence before stat —
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 — 721 functions depend on the 499 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_build_server()— 16 callers, 17 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_build_http_app()— 23 callers, 4 callees - new:
_score_query()— 15 callers, 6 callees - new:
_run_mcp_cli()— 9 callers, 7 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - …and 19 more — each is listed as a finding
Verification — 721 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: 709 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-753d71641c074f319b10dba73f9e4761/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
No difference found (not proven): No behavior difference found in \_resolve\_single\_node (not a proof).
The verifier ran both versions of \_resolve\_single\_node 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.
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-753d71641c074f319b10dba73f9e4761/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
· 14 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 dict(sorted(graphs.items())) | ||
|
|
||
|
|
||
| def _run_mcp_cli(args: list[str]) -> bool: |
There was a problem hiding this comment.
_run_mcp_cli()
fans out to 7 callees (efferent coupling); 9 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()
13 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); 16 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2013,17 +2218,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.
| @@ -2209,8 +2428,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
23 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 = 15).
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.
f1dc8c0 to
20ef370
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 serving mode: GraphRegistry loads one or many repository graphs (by path, paths, or named paths), resolves the target per-request via a graph param or session default, and rescan hot-reloads graphs whose graph.json mtime changed. Introduces list_graphs/use_graph tools alongside the existing single-graph tools, isolates the active-graph selection per HTTP session, and keeps the learning overlay off for multi-graph loads. Reworks graph load failures to raise GraphLoadError for CLI diagnostics instead of exiting mid-load, and reshapes the Docker/Compose entrypoint to run graphify /data --mcp against mounted read-only repos with GRAPHIFY_API_KEY.
Worth a look
- os module imported locally in _main but used at module scope by _discover_graphs —
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
- project_path allows arbitrary path traversal to load any graph.json on disk —
graphify/serve.py:1721· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- _discover_graphs uses os without a module-level import —
graphify/serve.py:2607· 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_path/from_paths derive graph name from different path levels causing inconsistent naming —
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
- session_states keyed by session object grows unbounded (leak) and setdefault is non-atomic across concurrent requests —
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 — 716 functions depend on the 499 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_build_server()— 16 callers, 17 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_build_http_app()— 23 callers, 4 callees - new:
_score_query()— 15 callers, 6 callees - new:
_main()— 11 callers, 7 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - …and 15 more — each is listed as a finding
Verification — 716 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: 541 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: 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-2fa8e6636c0742869241232e881b9b99/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
No difference found (not proven): No behavior difference found in \_resolve\_single\_node (not a proof).
The verifier ran both versions of \_resolve\_single\_node 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.
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-2fa8e6636c0742869241232e881b9b99/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
· 10 grounded finding(s) anchored inline below; 13 more finding(s) on lines outside this diff (see the check run).
|
|
||
|
|
||
| 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); 16 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.
| @@ -1815,11 +1974,24 @@ 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.
| @@ -1907,6 +2090,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 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1937,6 +2122,8 @@ def _tool_get_pr_impact(arguments: dict) -> str: | |||
| return "\n".join(lines) | |||
|
|
|||
| def _tool_triage_prs(arguments: dict) -> str: | |||
There was a problem hiding this comment.
_tool_triage_prs()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2013,17 +2231,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.
| @@ -2209,8 +2441,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
23 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return dict(sorted(graphs.items())) | ||
|
|
||
|
|
||
| def _main(argv: list[str] | None = None) -> None: |
There was a problem hiding this comment.
_main()
fans out to 7 callees (efferent coupling); 11 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.
20ef370 to
3acbaaf
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 serving mode: a GraphRegistry loads one or many repository graph.json files (via from_path/from_paths/from_named_paths), resolves a target per request through _resolve_graph with an error listing available names when ambiguous, and rescan hot-reloads changed graphs while evicting ones whose file disappeared. Exposes list_graphs and use_graph tools plus an optional graph parameter on existing tools, and switches the CLI/build_server to accept --graphs-dir; multi-graph mounts skip the learning overlay and project-path fallbacks. Reworks the Docker story around directory mounts — the image entrypoint and docker-compose.multi.yml now serve read-only --graphs-dir mounts keyed off GRAPHIFY_API_KEY, and .dockerignore/.gitignore exclude .env* and .worktrees/.
Worth a look
- _discover_graphs references os without a module-level import —
graphify/serve.py:2611· 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 image default command no longer accepts the documented legacy /data/graph.json mount —
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
- from_paths uses grandparent dir as name, causing collisions and wrong naming vs from_path —
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 keeps stale graph if file disappears during reload —
graphify/serve.py:116· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Single-graph resolution can return None if registry changes between names() and get() —
graphify/serve.py:154· 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 — 699 functions depend on the 482 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_build_server()— 16 callers, 17 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_build_http_app()— 23 callers, 4 callees - new:
_score_query()— 15 callers, 6 callees - new:
_main()— 11 callers, 7 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - …and 14 more — each is listed as a finding
Verification — 699 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: 524 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: 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-846033fd262544cf862e69d009fbdb57/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
No difference found (not proven): No behavior difference found in \_resolve\_single\_node (not a proof).
The verifier ran both versions of \_resolve\_single\_node 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.
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-846033fd262544cf862e69d009fbdb57/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
· 10 grounded finding(s) anchored inline below; 12 more finding(s) on lines outside this diff (see the check run).
|
|
||
|
|
||
| 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); 16 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.
| @@ -1815,11 +1980,24 @@ 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.
| @@ -1907,6 +2096,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 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1937,6 +2128,8 @@ def _tool_get_pr_impact(arguments: dict) -> str: | |||
| return "\n".join(lines) | |||
|
|
|||
| def _tool_triage_prs(arguments: dict) -> str: | |||
There was a problem hiding this comment.
_tool_triage_prs()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2013,17 +2237,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.
| @@ -2209,8 +2447,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
23 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return dict(sorted(graphs.items())) | ||
|
|
||
|
|
||
| def _main(argv: list[str] | None = None) -> None: |
There was a problem hiding this comment.
_main()
fans out to 7 callees (efferent coupling); 11 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.
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>
3acbaaf to
069d4cf
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 serving mode: GraphRegistry loads several graphify-out/graph.json files (via from_paths/from_named_paths/--graphs-dir), exposes list_graphs and use_graph plus a per-call graph parameter, and _resolve_graph picks the target from an explicit arg, a session default, or the sole loaded graph, erroring when the choice is ambiguous. Registry loads skip the learning overlay and reject duplicate repository names, and rescan reloads graphs on mtime change and drops ones whose files disappear, swallowing load failures rather than crashing the server. Reworks the Docker/Compose story around directory mounts and API-key env vars — the image now defaults to --graphs-dir /data, adds docker-compose.multi.yml for two read-only repo mounts over localhost HTTP, and documents that remote HTTP requires a nonblank API key.
Worth a look
- project_path allows arbitrary path traversal to load any graph.json on disk —
graphify/serve.py:1740· 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 image default command no longer supports the previously documented /data/graph.json mount contract —
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
- from_paths uses parent.parent.name causing collisions and wrong names vs from_path —
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
- request-context session state is keyed inconsistently —
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
- session_states dict grows unboundedly and is mutated without a 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
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 701 functions depend on the 484 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_build_server()— 16 callers, 17 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_build_http_app()— 24 callers, 4 callees - new:
_score_query()— 15 callers, 6 callees - new:
_main()— 11 callers, 7 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - …and 14 more — each is listed as a finding
Verification — 701 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: 526 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: 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-05490e4bcaef4e028b5380e0966f06f6/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
No difference found (not proven): No behavior difference found in \_resolve\_single\_node (not a proof).
The verifier ran both versions of \_resolve\_single\_node 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.
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-05490e4bcaef4e028b5380e0966f06f6/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
· 10 grounded finding(s) anchored inline below; 12 more finding(s) on lines outside this diff (see the check run).
|
|
||
|
|
||
| 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); 16 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.
| @@ -1815,11 +1987,24 @@ 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.
| @@ -1907,6 +2103,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 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -1937,6 +2135,8 @@ def _tool_get_pr_impact(arguments: dict) -> str: | |||
| return "\n".join(lines) | |||
|
|
|||
| def _tool_triage_prs(arguments: dict) -> str: | |||
There was a problem hiding this comment.
_tool_triage_prs()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2013,17 +2244,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.
| @@ -2209,8 +2461,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
24 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return dict(sorted(graphs.items())) | ||
|
|
||
|
|
||
| def _main(argv: list[str] | None = None) -> None: |
There was a problem hiding this comment.
_main()
fans out to 7 callees (efferent coupling); 11 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.
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: serve can now be pointed at a directory with --graphs-dir (or multiple graph paths) and exposes every repository's graphify-out/graph.json from one endpoint, adding list_graphs/use_graph tools and an optional graph parameter on each existing tool. Introduces a GraphRegistry that loads, per-request-reloads on mtime change, and periodically rediscovers graphs on a configurable --graph-scan-interval (default 30s, 0 to rescan every request), dropping graphs whose files disappear and skipping ones that fail to load. Reworks the Docker image and adds docker-compose.multi.yml to serve read-only-mounted repos over localhost HTTP with a required GRAPHIFY_API_KEY, and updates the README/Dockerfile invocation accordingly.
Worth a look
- _discover_graphs uses os without a module-level import —
graphify/serve.py:2671· 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 image default command no longer serves the documented mounted graph file —
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
- rescan swallows SystemExit from _load_graph, but from_named_paths/discovery path can still exit process on legacy loader —
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() re-loads graph while holding lock, blocking all get()/names() callers —
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
- os imported at module scope may shadow local import in _main —
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 — 716 functions depend on the 499 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_build_server()— 17 callers, 17 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
_query_graph_text()— 20 callers, 10 callees - new:
_main()— 16 callers, 6 callees - new:
_build_http_app()— 24 callers, 4 callees - new:
_score_query()— 15 callers, 6 callees - new:
_get_ctx()— 9 callers, 7 callees - new:
_query_terms()— 20 callers, 3 callees - …and 15 more — each is listed as a finding
Verification — 716 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: 541 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: 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-0d7a6f4dce554d389f9dc90e9fc305f7/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
No difference found (not proven): No behavior difference found in \_resolve\_single\_node (not a proof).
The verifier ran both versions of \_resolve\_single\_node 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.
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-0d7a6f4dce554d389f9dc90e9fc305f7/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
· 11 grounded finding(s) anchored inline below; 12 more finding(s) on lines outside this diff (see the check run).
| ) | ||
|
|
||
|
|
||
| def _load_graph_or_raise(graph_path: str, *, load_learning_overlay: bool = True) -> nx.Graph: |
There was a problem hiding this comment.
_load_graph_or_raise()
high coupling complexity (Ca·Ce = 15).
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); 17 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.
| @@ -1815,11 +2030,24 @@ 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.
| @@ -1907,6 +2146,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 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -2013,17 +2287,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.
| @@ -2209,8 +2504,9 @@ async def __call__(self, scope, receive, send) -> None: | |||
|
|
|||
|
|
|||
| def _build_http_app( | |||
There was a problem hiding this comment.
_build_http_app()
24 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return dict(sorted(graphs.items())) | ||
|
|
||
|
|
||
| def _main(argv: list[str] | None = None) -> None: |
There was a problem hiding this comment.
_main()
fans out to 6 callees (efferent coupling); 16 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.
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