fix: preserve edge direction in merge-graphs - #2271
Conversation
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR modifies edge direction handling in the global graph merge flow. In prefix_graph_for_global (build.py), it extends the node-relabeling logic to also rewrite the _src/_tgt directional edge attributes to use the newly prefixed node IDs. In the merge-graphs CLI dispatch (cli.py), it adds logic to stash _src/_tgt markers when loading graphs into an undirected graph and to restore original source/target from those markers when serializing the merged output. The changes add two new tests: one asserting that prefixed graphs update directional edge attributes, and one end-to-end test of merge-graphs covering node prefixing, counts, and edge direction preservation across two repos.
No blocking issues surfaced. 5 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
graphify review
Impact — 769 function(s) in the blast radius of 139 changed node(s).
Health — grade B; 10 existing offender(s) in the touched area:
- [high]
_rebuild_code()(Ca=55 Ce=48) - [high]
build_from_json()(Ca=138 Ce=13) - [high]
dispatch_command()(Ca=2 Ce=109) - [high]
build_merge()(Ca=23 Ce=7) - [high]
main()(Ca=79 Ce=2) - [high]
generate()(Ca=29 Ce=5) - [high]
to_obsidian()(Ca=24 Ce=6) - [high]
_make_graph()(Ca=24 Ce=6) - [high]
to_wiki()(Ca=33 Ce=4) - [high]
diagnose_extraction()(Ca=12 Ce=9)
Verification — 769 function(s) need re-proving (callee-first):
graphify_build_edge_data → graphify_wiki_god_node_article → graphify_wiki_community_article → graphify_wiki_to_wiki → tests_test_wiki_test_wiki_special_characters_in_label_resolve → tests_test_wiki_test_wiki_links_use_collision_suffixed_slug → tests_test_wiki_test_wiki_links_to_nodes_without_articles_are_plain_text → tests_test_wiki_test_wiki_links_resolve_to_real_files → tests_test_wiki_test_wiki_link_with_bracketed_label_resolves → tests_test_wiki_test_wiki_link_display_keeps_label_but_target_is_filename …
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not enforced this run (re-run with --prove to verify the blast radius)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 712 function(s) in blast radius — re-run with
--proveto formally verify them
Summary
Fixes #2261.
graphify merge-graphscould silently rewrite the direction of relationship edges when merging persistedgraph.jsonfiles. Although the merged graph preserved the correct node and edge counts, directional relationships such asimportsandimports_fromcould be reversed, producing incorrect dependency information (for example, a file appearing to import itself instead of the intended module).This change preserves the original edge direction throughout the merge process while keeping the merged graph format unchanged (
directed: false), matching the existingmerge-graphscontract.Root Cause
Persisted
graph.jsonfiles are stored as undirected graphs ("directed": false). Duringmerge-graphs, they were loaded into an undirectednx.Graph, which does not preserve edge orientation. When the merged graph was serialized back to JSON, NetworkX emitted edge endpoints according to its internal ordering, causing some directional relationships to be written with swappedsourceandtargetvalues.Solution
prefix_graph_for_global()to remap directional edge metadata alongside prefixed node IDs, ensuring internal consistency after relabeling.Tests
Added regression coverage for Issue #2261 by verifying that:
merge-graphs,Also added a focused unit test for
prefix_graph_for_global()to verify that directional edge metadata is correctly updated when node IDs are prefixed.Validation
Ran:
Both test suites pass successfully.