graphifyy v0.7.16 — graphify explain renders inbound edges as outbound (structural twin of #849)
Package: graphifyy v0.7.16 (pip install graphifyy)
Environment: macOS, Python 3.14.4
Related: #849 — same root cause as graphify path, fixed there in PR #851.
graphify explain prints every neighbor as --> X [relation], regardless of whether the underlying edge actually points out of the queried node or into it. For a queried callee, all its callers are rendered as outbound calls — asserting a backwards call graph.
I noted this as out-of-scope in #851; now confirmed in the wild on a real codebase.
Reproducer
Same fixture as #849. Source (real, two-file cross-file call):
// server/create-patch-handler.ts:28
const user = await validateSanitySession({ projectId: opts.projectId, credential });
So the true direction is createPatchHandler --calls--> validateSanitySession. validateSanitySession() is a leaf — it has no outbound calls edges; everything that touches it is inbound.
Build & query:
$ graphify update . --force
[graphify watch] Rebuilt: 192 nodes, 416 edges, 10 communities
$ graphify explain "validateSanitySession()"
Node: validateSanitySession()
...
Connections (13):
--> create-edit-handler.ts [imports] [EXTRACTED]
--> createPatchHandler() [calls] [EXTRACTED]
--> createEditHandler() [calls] [EXTRACTED]
--> createAuthHandler() [calls] [EXTRACTED]
...
Every arrow should be <-- — these are all callers/importers of validateSanitySession, not things it calls.
Root cause
Same as #849 — the build serializes directed: false, __main__.py's cmd == "explain" branch calls json_graph.node_link_graph(_raw, edges="links") and gets an undirected nx.Graph. Then:
neighbors = list(G.neighbors(nid)) # all neighbors, direction lost
for nb in sorted(neighbors, ...)[:20]:
edata = edge_data(G, nid, nb)
...
print(f" --> {G.nodes[nb].get('label', nb)} [{rel}] [{conf}]")
Hard-coded -->. The renderer assumes outbound because G.neighbors() on an undirected graph returns everything as if it were outbound from the queried node.
Suggested fix (mirrors #851)
Force directed interpretation on read, then iterate successors and predecessors separately:
_raw = {**_raw, "directed": True}
...
G = json_graph.node_link_graph(_raw, edges="links")
...
connections = []
for nb in G.successors(nid):
connections.append(("out", nb, edge_data(G, nid, nb)))
for nb in G.predecessors(nid):
connections.append(("in", nb, edge_data(G, nb, nid)))
# Sort by neighbor degree, render '-->' / '<--' per direction.
A neighbor that has both inbound and outbound edges to the queried node (mutual references) gets rendered twice — once per direction — which is the truthful directed representation.
Impact
Same shape as #849 — explain output is what an assistant relays verbatim when a user asks "what is X / what touches X?". Wrong arrows assert a backwards graph.
PR with fix + tests incoming.
graphifyyv0.7.16 —graphify explainrenders inbound edges as outbound (structural twin of #849)Package:
graphifyyv0.7.16 (pip install graphifyy)Environment: macOS, Python 3.14.4
Related: #849 — same root cause as
graphify path, fixed there in PR #851.graphify explainprints every neighbor as--> X [relation], regardless of whether the underlying edge actually points out of the queried node or into it. For a queried callee, all its callers are rendered as outbound calls — asserting a backwards call graph.I noted this as out-of-scope in #851; now confirmed in the wild on a real codebase.
Reproducer
Same fixture as #849. Source (real, two-file cross-file call):
So the true direction is
createPatchHandler --calls--> validateSanitySession.validateSanitySession()is a leaf — it has no outboundcallsedges; everything that touches it is inbound.Build & query:
Every arrow should be
<--— these are all callers/importers ofvalidateSanitySession, not things it calls.Root cause
Same as #849 — the build serializes
directed: false,__main__.py'scmd == "explain"branch callsjson_graph.node_link_graph(_raw, edges="links")and gets an undirectednx.Graph. Then:Hard-coded
-->. The renderer assumes outbound becauseG.neighbors()on an undirected graph returns everything as if it were outbound from the queried node.Suggested fix (mirrors #851)
Force directed interpretation on read, then iterate successors and predecessors separately:
A neighbor that has both inbound and outbound edges to the queried node (mutual references) gets rendered twice — once per direction — which is the truthful directed representation.
Impact
Same shape as #849 —
explainoutput is what an assistant relays verbatim when a user asks "what is X / what touches X?". Wrong arrows assert a backwards graph.PR with fix + tests incoming.