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
make_id(stem, entity) strips non-alphanumeric characters from entity. A Dart private named constructor (class Foo { const Foo._(); }) has the entity name _, which normalizes to the empty string — so the symbol's ID collapses to the bare file stem, which is the ID that file's own node already owns.
On build the two nodes merge and the symbol's _ label overwrites the filename label.
That second-order effect is the damaging one. analyze._is_file_node() identifies file-level hubs by comparing the label against the source filename:
Once the label is _ instead of foo.dart, the node is no longer recognised as a file node — so it leaks straight into god_nodes(), surprising_connections() and knowledge-gap reporting, which are exactly the consumers the file-node filter exists to protect. god_nodes()' own docstring says file hubs "accumulate import/contains edges mechanically and don't represent meaningful architectural abstractions", and that intent is silently defeated.
The two top entries of the report's Suggested Questions section were also built on these nodes ("Why does _ connect ... to <80 communities>"), so the highest-signal part of the generated report was pointed at an artifact.
236 of the 246 had no sibling node sharing the ID, so the collapsed symbol is the sole occupant of the file-stem ID — for those files the filename label is lost outright rather than merely contested.
Since /graphify's report presents the God Nodes list to the user as "your core abstractions", this reads as a confident, wrong answer about the codebase's architecture rather than as a visible glitch.
Suggested fix
Primary — keep the symbol's ID distinct. In make_id, when a non-empty entity was supplied but normalizes to empty, fall back to a stable placeholder rather than silently dropping the segment, so the symbol never lands on the file-stem ID:
# entity was given but is all-punctuation (Dart `Foo._()`, a bare `_`);# without this the ID collapses onto the file node and overwrites its label.ifraw_entityandnotnormalized_entity:
normalized_entity="underscore"# or a short stable hash of raw_entity
Secondary — make the merge non-destructive. When build merges two nodes sharing an ID, prefer the label that matches the source filename over one that does not. That keeps a single bad label from disabling _is_file_node even if some other construct collapses in future.
Not Dart-specific
A bare _ is idiomatic in Rust, Go and Python too, and any all-punctuation identifier hits the same empty-entity path. I only verified the Dart case, but the collapse is in the shared make_id, so it is worth checking whether the other extractors emit nodes for such identifiers.
Workaround
For anyone hitting this before a fix lands — relabel rather than filter, since the edges are real. After merging the extraction and before build_from_json:
Then dedupe by ID. On the corpus above this restored all 246 labels and cost 0 edges; god nodes became the real abstractions, and the file node kept its 409 edges while correctly dropping out of the abstraction ranking. Filtering the _ nodes out instead is worse: it discards 862 real edges and fragmented that graph from 395 to 641 communities.
Summary
make_id(stem, entity)strips non-alphanumeric characters fromentity. A Dart private named constructor (class Foo { const Foo._(); }) has the entity name_, which normalizes to the empty string — so the symbol's ID collapses to the bare file stem, which is the ID that file's own node already owns.On build the two nodes merge and the symbol's
_label overwrites the filename label.That second-order effect is the damaging one.
analyze._is_file_node()identifies file-level hubs by comparing the label against the source filename:Once the label is
_instead offoo.dart, the node is no longer recognised as a file node — so it leaks straight intogod_nodes(),surprising_connections()and knowledge-gap reporting, which are exactly the consumers the file-node filter exists to protect.god_nodes()' own docstring says file hubs "accumulate import/contains edges mechanically and don't represent meaningful architectural abstractions", and that intent is silently defeated.Reproduction
graphify 0.9.42, Python 3.13, macOS.
lib/widget.dart— 5 lines:The collapse is directly visible in
make_id:__collapses identically. And afterbuild_from_json:Impact observed on a real corpus
A ~1,050-file Dart/Flutter codebase,
lib/only, 10,152 nodes / 17,941 edges:_._. The Wroked out examples missing graph.html #3 was another file node at 43 edges. Both are precisely what_is_file_nodeis meant to exclude; with correct labels neither appears in the ranking at all._connect ... to <80 communities>"), so the highest-signal part of the generated report was pointed at an artifact.Since
/graphify's report presents the God Nodes list to the user as "your core abstractions", this reads as a confident, wrong answer about the codebase's architecture rather than as a visible glitch.Suggested fix
Primary — keep the symbol's ID distinct. In
make_id, when a non-emptyentitywas supplied but normalizes to empty, fall back to a stable placeholder rather than silently dropping the segment, so the symbol never lands on the file-stem ID:Secondary — make the merge non-destructive. When
buildmerges two nodes sharing an ID, prefer the label that matches the source filename over one that does not. That keeps a single bad label from disabling_is_file_nodeeven if some other construct collapses in future.Not Dart-specific
A bare
_is idiomatic in Rust, Go and Python too, and any all-punctuation identifier hits the same empty-entity path. I only verified the Dart case, but the collapse is in the sharedmake_id, so it is worth checking whether the other extractors emit nodes for such identifiers.Workaround
For anyone hitting this before a fix lands — relabel rather than filter, since the edges are real. After merging the extraction and before
build_from_json:Then dedupe by ID. On the corpus above this restored all 246 labels and cost 0 edges; god nodes became the real abstractions, and the file node kept its 409 edges while correctly dropping out of the abstraction ranking. Filtering the
_nodes out instead is worse: it discards 862 real edges and fragmented that graph from 395 to 641 communities.