You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Title: Elixir extractor: unqualified bare-name calls attach to unrelated same-named nodes, producing fake god nodes
Summary
The Elixir AST extractor resolves unqualified function calls by bare name, ignoring import scope and arity. When a name is defined somewhere in the repo (so a definition-anchored node exists) and called elsewhere as a DSL/framework function, every call site attaches to the definition node. One node can collect hundreds of false calls edges, become the #1 god node, and distort Louvain clustering.
table() — node anchored at apps/ecosystem_web/lib/ecosystem_web/components/core_components.ex:677 (a Phoenix function component). Collected 690 calls edges whose source_file is a migration file (change()/up()/down() callers across 8 apps) — these are Ecto.Migration table/2 usages. Result: 691-edge v3: semantic query with embeddings #1 god node, clustered with migrations instead of UI components.
remove() — anchored at an ETS registry helper (agent_session_registry.ex:91); 106 edges from migrations (Ecto.Migration remove/1).
execute() — two separate definition-anchored nodes (a mix task and a seeds file) each collected migration execute/1 edges (54 + 38).
create() ×4, add(), index() — same pattern (Ecto.Migration DSL, anchored in unrelated manager/notification/config files).
select() — anchored at a domain function (membership.ex:12); 147 edges from other apps' Ecto.Query pipelines.
build() — anchored at a test factory; 109 edges from other apps' factory usages (ExMachina build/2).
push_flash() — anchored at one LiveView; 89 edges from other components (Phoenix.LiveView push_flash/3).
Root cause
Call-edge resolution does not consider the caller's imports/aliases. The only node that exists for a bare name is wherever the name happens to be defined; unqualified DSL calls therefore join it.
Suggested fix (either)
Resolve unqualified calls against the caller module's import/alias/require set before matching a node, or
When the call-site file differs from the def-site file and the bare name matches a known DSL surface (Ecto.Migration, Ecto.Query, Kernel, ExMachina, Phoenix.LiveView), emit a qualified external-dependency node (e.g. ecto_migration_table, label "Ecto.Migration table/2") instead of joining the definition node.
Workaround we used (happy to contribute)
Detection: any node whose source_file is not a migration context but whose incident edges are >50% from migration paths (degree ≥ 5), plus known framework names with >20 cross-file calls edges. Repair: re-point those edges to external DSL nodes (confidence: INFERRED, 0.95), keep the definition node's real local edges, re-cluster. 1,212 edges re-pointed across 13 nodes; god-node list went from 4/10 artifacts to a clean ranking. Script available on request.
Upstream issue draft for Graphify-Labs/graphify
Title: Elixir extractor: unqualified bare-name calls attach to unrelated same-named nodes, producing fake god nodes
Summary
The Elixir AST extractor resolves unqualified function calls by bare name, ignoring import scope and arity. When a name is defined somewhere in the repo (so a definition-anchored node exists) and called elsewhere as a DSL/framework function, every call site attaches to the definition node. One node can collect hundreds of false
callsedges, become the #1 god node, and distort Louvain clustering.Observed (repo: Elixir umbrella, ~10 apps, migrations under
priv/repo/migrations/; graphifyy 0.9.56)table()— node anchored atapps/ecosystem_web/lib/ecosystem_web/components/core_components.ex:677(a Phoenix function component). Collected 690callsedges whosesource_fileis a migration file (change()/up()/down()callers across 8 apps) — these are Ecto.Migrationtable/2usages. Result: 691-edge v3: semantic query with embeddings #1 god node, clustered with migrations instead of UI components.remove()— anchored at an ETS registry helper (agent_session_registry.ex:91); 106 edges from migrations (Ecto.Migrationremove/1).execute()— two separate definition-anchored nodes (a mix task and a seeds file) each collected migrationexecute/1edges (54 + 38).create()×4,add(),index()— same pattern (Ecto.Migration DSL, anchored in unrelated manager/notification/config files).select()— anchored at a domain function (membership.ex:12); 147 edges from other apps' Ecto.Query pipelines.build()— anchored at a test factory; 109 edges from other apps' factory usages (ExMachinabuild/2).push_flash()— anchored at one LiveView; 89 edges from other components (Phoenix.LiveViewpush_flash/3).Root cause
Call-edge resolution does not consider the caller's imports/aliases. The only node that exists for a bare name is wherever the name happens to be defined; unqualified DSL calls therefore join it.
Suggested fix (either)
import/alias/requireset before matching a node, orecto_migration_table, label "Ecto.Migration table/2") instead of joining the definition node.Workaround we used (happy to contribute)
Detection: any node whose
source_fileis not a migration context but whose incident edges are >50% from migration paths (degree ≥ 5), plus known framework names with >20 cross-filecallsedges. Repair: re-point those edges to external DSL nodes (confidence: INFERRED, 0.95), keep the definition node's real local edges, re-cluster. 1,212 edges re-pointed across 13 nodes; god-node list went from 4/10 artifacts to a clean ranking. Script available on request.