Skip to content

/graphify --update: build_merge called without root= → prune_sources never matches, graph inflates on every update #1361

Description

@bchan84x

Summary

The /graphify <path> --update skill runbook calls build_merge(prune_sources=...) without passing root=, so the abs/rel path normalization added for #1007 is bypassed. prune_sources (absolute paths) never matches the graph's relative source_file values, nothing is pruned, and the graph accumulates duplicate/stale nodes on every incremental update. The native graphify update CLI is unaffected — it passes root.

Version: graphifyy 0.8.38

Root cause

build_merge only relativizes prune_sources when root is provided (build.py):

_root_str = str(Path(root).resolve()) if root is not None else None
prune_set = set()
for p in prune_sources:
    prune_set.add(p)
    norm = _norm_source_file(p, _root_str)   # no-op when _root_str is None
    if norm:
        prune_set.add(norm)
to_remove = [n for n, d in G.nodes(data=True) if d.get("source_file") in prune_set]

_norm_source_file(p, None) returns p unchanged for absolute paths (the if root and os.path.isabs(p) guard fails). Graph nodes store relative source_file, so d.get("source_file") in prune_set never matches.

The shipped update runbook (references/update.md, ~line 104) calls it without root:

G = build_merge(
    [new_extraction],
    graph_path='graphify-out/graph.json',
    prune_sources=prune,
)

and prune is built from detect_incremental(...)'s new_files / deleted_files, which are absolute paths — so the mismatch is guaranteed.

By contrast the native CLI (__main__.py, incremental branch) does it correctly:

G = _build_merge([merged], graph_path=existing_graph_path,
                 prune_sources=deleted_files or None,
                 dedup=True, dedup_llm_backend=dedup_backend, root=target)

Observed

Running /graphify . --update after editing 4 tracked docs:

[graphify] 4 source file(s) deleted since last run — no matching nodes or edges in graph, already clean.
[update] Merged: 737 nodes   # ← 4 changed files' OLD nodes survived; 28 new appended

Re-running build_merge with the same prune list relativized against root:

[graphify] Pruned 84 node(s) from 4 deleted source file(s).
[update] Merged: 653 nodes   # ← correct

Over repeated --update runs this compounds, inflating the graph with ghost duplicates of every changed file.

Suggested fix

Pass root in the update runbook (and any other skill flow that calls build_merge with prune_sources):

G = build_merge(
    [new_extraction],
    graph_path='graphify-out/graph.json',
    prune_sources=prune,
    root=str(Path('.').resolve()),   # or the scan root recorded in graphify-out/.graphify_root
)

Optionally, make build_merge defensive: when root is None, default _root_str to graph_path.parent.parent (the project root, since the graph lives at graphify-out/graph.json) so prune still works when a caller forgets root.

Environment

  • graphifyy 0.8.38, Python 3.12, Linux
  • Repro is platform-independent (absolute-vs-relative source_file mismatch), but it bites hardest right after a checkout moves between machines/OSes, where the manifest baseline already forces a large changed-file set.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions