mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-28 17:26:48 +00:00
fix(extract): drop internal origin_file so it stops leaking into graph.json (#1516)
The origin_file disambiguation field (#1462) is internal — it's consumed by the colliding-id pass and must not be persisted. In 0.9.0 it shipped into graph.json as an absolute, machine-specific path (the same portability bug class as #555, #932), breaking on clone/move. It's now popped at the single extract() output chokepoint, AFTER _disambiguate_colliding_node_ids has consumed it, so all persist paths (clustered, --no-cluster, cluster-only) emit clean output. _origin is kept (the incremental watcher relies on it, #1116). Ported from PR #1516 by @TPAteeq. Verified: fresh extract (clustered and --no-cluster) produces graph.json with zero origin_file fields; the #1462 same- label disambiguation still works (it runs before the strip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
safishamsi
co-authored by
Claude Opus 4.8
parent
94e5bafc89
commit
afa4aded2e
@@ -2,6 +2,10 @@
|
||||
|
||||
Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fix: the internal `origin_file` disambiguation field (#1462) is no longer serialized into graph.json, where it had shipped (in 0.9.0) as an absolute, machine-specific path — it is dropped once the colliding-id pass consumes it, keeping output portable (#1516, thanks @TPAteeq; cf. #555, #932). `_origin` stays (the incremental watcher needs it, #1116).
|
||||
|
||||
## 0.9.0 (2026-06-28)
|
||||
|
||||
- **Breaking — node IDs now include the full repo-relative path** (#1504, #1509). The node-ID stem was the immediate parent dir + filename, so same-named files in different directories collided into one last-writer-wins node and silently dropped graph content (`docs/v1/api/README.md` and `docs/v2/api/README.md` both → `api_readme`). The stem is now the full repo-relative path (`docs_v1_api_readme` vs `docs_v2_api_readme`); top-level files are unchanged (`setup.py` → `setup`). The AST extractor, the LLM system prompt, the extraction-spec, and the two hand-copied stem helpers are all aligned to this one rule (fixing the #1509 AST↔LLM divergence that produced ghost duplicates), and `build_from_json` deterministically re-keys any cached/older semantic fragment onto the new IDs from its `source_file` so the unversioned semantic cache survives without ghosts or a re-bill. **Existing graphs migrate to the new ID format automatically on the next `build`/`update`** (no re-bill). Note: same-named files in different directories that previously collided into one node are only *recovered as distinct nodes* by a fresh extraction — run `graphify extract --force` to rebuild and gain them (migrating an already-collided graph/cache can't resurrect the nodes that were already dropped). If you push to a persisted **Neo4j** store, re-import after upgrading (re-exported IDs change); saved Gephi/yEd (GraphML) layouts go stale; MCP/cypher consumers should query by label rather than persisting node IDs across rebuilds.
|
||||
|
||||
@@ -13466,6 +13466,15 @@ def extract(
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# origin_file is an internal disambiguation hint (#1462): the colliding-id pass
|
||||
# above reads it to keep same-named cross-file stubs distinct, after which nothing
|
||||
# consumes it. Drop it from the returned nodes so it never ships into graph.json as
|
||||
# an absolute, machine-specific path — the same "no absolute paths in output"
|
||||
# contract that relativizes source_file just above (#555, #932). The per-file AST
|
||||
# cache keeps its own copy, which is what the colliding-id pass reads on a cache hit.
|
||||
for n in all_nodes:
|
||||
n.pop("origin_file", None)
|
||||
|
||||
# Tag AST provenance so the incremental watch rebuild can distinguish
|
||||
# AST-extracted nodes from semantic/LLM nodes. On a full re-extraction
|
||||
# the watcher drops any AST-marked node missing from the fresh output
|
||||
|
||||
@@ -171,6 +171,36 @@ def test_imported_type_stubs_do_not_collide_across_source_files(tmp_path):
|
||||
assert all(not node.get("source_file") for node in path_nodes)
|
||||
|
||||
|
||||
def test_origin_file_is_not_serialized_into_extract_output(tmp_path):
|
||||
"""origin_file is an internal disambiguation hint (#1462) consumed only by the
|
||||
colliding-id pass during extraction. It must not survive into the returned nodes
|
||||
(and thus graph.json), where it would ship as an absolute, machine-specific path —
|
||||
the "no absolute paths in output" contract (#555, #932). Disambiguation still keys
|
||||
on it first, so the two same-label cross-file stubs stay distinct."""
|
||||
first = tmp_path / "pkg/a.py"
|
||||
second = tmp_path / "pkg/b.py"
|
||||
first.parent.mkdir(parents=True)
|
||||
first.write_text("from pathlib import Path\ndef use_a(p: Path):\n return p\n", encoding="utf-8")
|
||||
second.write_text("from pathlib import Path\ndef use_b(p: Path):\n return p\n", encoding="utf-8")
|
||||
|
||||
result = extract([first, second], cache_root=tmp_path)
|
||||
|
||||
# The internal field is gone from every node...
|
||||
assert all("origin_file" not in node for node in result["nodes"])
|
||||
# ...so no node leaks the absolute sandbox path that origin_file used to carry.
|
||||
leaked = [
|
||||
(node.get("id"), key, value)
|
||||
for node in result["nodes"]
|
||||
for key, value in node.items()
|
||||
if isinstance(value, str) and str(tmp_path) in value
|
||||
]
|
||||
assert not leaked, f"absolute paths leaked into nodes: {leaked}"
|
||||
# ...yet the colliding-id pass still kept the two cross-file stubs distinct.
|
||||
path_nodes = [node for node in result["nodes"] if node["label"] == "Path"]
|
||||
assert len(path_nodes) == 2
|
||||
assert len({node["id"] for node in path_nodes}) == 2
|
||||
|
||||
|
||||
def test_extract_updates_raw_call_callers_after_duplicate_id_disambiguation(tmp_path):
|
||||
first = tmp_path / "apps/api/Program.cs"
|
||||
second = tmp_path / "tools/api/Program.cs"
|
||||
|
||||
Reference in New Issue
Block a user