MCPcopy Create free account
hub / github.com/Graphify-Labs/graphify / build_merge

Function build_merge

graphify/build.py:735–909  ·  view source on GitHub ↗

Load existing graph.json, merge new chunks into it, and save back. Re-extracted files REPLACE their prior contribution: any source_file present in new_chunks is dropped from the loaded graph before merging, so a changed file's stale nodes/edges don't accumulate. Files absent from new_ch

(
    new_chunks: list[dict],
    graph_path: str | Path | None = None,
    prune_sources: list[str] | None = None,
    *,
    directed: bool = False,
    dedup: bool = True,
    dedup_llm_backend: str | None = None,
    root: str | Path | None = None,
)

Source from the content-addressed store, hash-verified

733
734
735def build_merge(
736 new_chunks: list[dict],
737 graph_path: str | Path | None = None,
738 prune_sources: list[str] | None = None,
739 *,
740 directed: bool = False,
741 dedup: bool = True,
742 dedup_llm_backend: str | None = None,
743 root: str | Path | None = None,
744) -> nx.Graph:
745 """Load existing graph.json, merge new chunks into it, and save back.
746
747 Re-extracted files REPLACE their prior contribution: any source_file present
748 in new_chunks is dropped from the loaded graph before merging, so a changed
749 file's stale nodes/edges don't accumulate. Files absent from new_chunks are
750 preserved unchanged; deleted files are removed via prune_sources.
751 Safe to call repeatedly.
752 root: if given, absolute source_file paths in new_chunks are made relative (#932).
753 """
754 graph_path = Path(graph_path if graph_path is not None else _default_graph_json())
755 if graph_path.exists():
756 # Read JSON directly instead of going through node_link_graph().
757 # The latter rebuilds an undirected nx.Graph and then enumerating
758 # edges() yields endpoints based on node insertion order, which
759 # silently flips directional edges (e.g. `calls`) when the callee
760 # was inserted before the caller. The _src/_tgt direction-preserving
761 # attrs are popped before saving in export.py, so going through the
762 # NetworkX round-trip loses direction permanently (#760).
763 from graphify.security import check_graph_file_size_cap
764 check_graph_file_size_cap(graph_path)
765 try:
766 data = json.loads(graph_path.read_text(encoding="utf-8"))
767 except (json.JSONDecodeError, OSError) as exc:
768 raise RuntimeError(
769 f"Cannot read {graph_path} for incremental merge: {exc}. "
770 "Delete the file and run a full rebuild."
771 ) from exc
772 links_key = "links" if "links" in data else "edges"
773 existing_nodes = list(data.get("nodes", []))
774 existing_edges = list(data.get(links_key, []))
775 existing_hyperedges = list(data.get("hyperedges", []))
776 had_graph = True
777 else:
778 existing_nodes = []
779 existing_edges = []
780 existing_hyperedges = []
781 had_graph = False
782
783 # Effective root for relativizing absolute source_file / prune paths back to the
784 # stored relative source_file keys. When the caller passes root we use it;
785 # otherwise fall back to the graph's recorded scan root, so absolute
786 # prune_sources and new-chunk paths still match even when a caller omits root
787 # (#1571 — the skill's --update runbook calls build_merge without root, so
788 # absolute deleted-file paths never matched the relative node keys and their
789 # nodes survived as ghosts).
790 _eff_root = (
791 str(Path(root).resolve()) if root is not None
792 else _infer_merge_root(graph_path)

Calls 7

attach_hyperedgesFunction · 0.90
_infer_merge_rootFunction · 0.85
_norm_source_fileFunction · 0.85
_keptFunction · 0.85
buildFunction · 0.70
getMethod · 0.45