External import stubs (#2873) are minted without an _origin stamp. On
the next update the #2334 tier backfill stamped them by shape, and a
stub has no L<line> location, so it became `semantic`. Reconcile keeps
semantic nodes, so once the last file importing a module stopped
importing it, the stub stayed in graph.json forever with no edge. It
also made a rebuild of an unchanged tree differ from the first build.
A stub is derived from its import edges and minted again on every
write, so it has no tier. The backfill (now one helper shared by
_load_existing_graph and the reconcile path) skips stubs and removes a
stamp an earlier run gave one; every tier decision reads an unstamped
stub exactly as before. Reconcile drops a preserved stub that no fresh
or preserved edge references.
On graphify's own repo a warm rebuild now equals the cold build (125
stub nodes differed), a long-lived graph heals to the cold node set
(8 ghost stubs removed), and the changed-files path equals the full
rebuild for edits and additions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d3fbf6458f)
graph.json shipped imports/imports_from/re_exports edges whose target was an
external module (stdlib, a third-party dep) with no entry in nodes[]. Every
loader (networkx node_link_graph) materialises such an endpoint as an
attribute-less phantom, so declared node-count != loaded node-count, and
merge-graphs fragmented the same module into one node per repo.
- build_from_json now mints a typed external stub node (type=external,
external=True) for an import-family edge whose target is otherwise undeclared,
instead of dropping the edge. Non-import relations are unchanged, so a
sourceless external call target stays suppressed (#3156, no phantom god-node).
- The --no-cluster / incremental update path writes the raw merged extraction
rather than a builder graph, so it applies the same stubbing via the shared
mint_external_stubs_in_data helper. Idempotent; grows the node set so the
#479 shrink guard is unaffected.
- prefix_graph_for_global leaves external stubs unprefixed (no repo:: tag), so
the same stdlib/dependency module unifies into one global node across a
cross-repo merge instead of fragmenting per repo.
Adds regression tests for build-level stubbing, serialized-graph consistency,
the #3156 non-import drop, idempotency, cross-repo unification, and an
end-to-end `update --no-cluster` integrity check through the real rebuild.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>