diff --git a/graphify/build.py b/graphify/build.py index bdd56a31..a3ef6f30 100644 --- a/graphify/build.py +++ b/graphify/build.py @@ -201,15 +201,20 @@ def graph_has_legacy_ids(nodes: list, root: str | Path | None = None, sample: in stem) rather than the full repo-relative path. Read-only consumers (query, serve) use this to nudge the user to rebuild, since they don't re-extract. - Heuristic and cheap: samples nodes with a relative ``source_file`` and returns - True as soon as one ID matches an OLD stem form but NOT the canonical full-path - form. Absolute/sourceless nodes are skipped (can't be classified).""" + Heuristic and cheap: only **file-level** nodes (source_location ``L1``) are + inspected, because their ID is unambiguously the file stem. Symbol nodes are + skipped — some extractors scope a symbol by package/directory (Go's + ``_make_id(pkg_dir, name)`` → ``sub_thing``), which can coincide with an old + file-stem form and would otherwise false-positive. Returns True as soon as one + file node's ID matches an OLD stem form but not the canonical full-path form.""" from graphify.extractors.base import _file_stem _r = str(root) if root is not None else None checked = 0 for node in nodes: if not isinstance(node, dict): continue + if str(node.get("source_location") or "") != "L1": + continue # only file-level nodes carry an unambiguous file-stem ID nid = node.get("id") sf = node.get("source_file") if not nid or not isinstance(nid, str) or not sf: diff --git a/tests/test_build.py b/tests/test_build.py index 8a5d9dc9..2d8bfdd6 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -726,13 +726,18 @@ def test_graph_has_legacy_ids_detects_old_scheme(): """The read-only-consumer nudge (query/serve) flags a pre-#1504 graph and leaves a canonical one alone.""" from graphify.build import graph_has_legacy_ids - old = [{"id": "api_readme", "source_file": "docs/v1/api/README.md", "type": "document"}] - new = [{"id": "docs_v1_api_readme", "source_file": "docs/v1/api/README.md", "type": "document"}] + old = [{"id": "api_readme", "source_file": "docs/v1/api/README.md", "type": "document", "source_location": "L1"}] + new = [{"id": "docs_v1_api_readme", "source_file": "docs/v1/api/README.md", "type": "document", "source_location": "L1"}] assert graph_has_legacy_ids(old, root=".") is True assert graph_has_legacy_ids(new, root=".") is False - # sourceless / top-level nodes don't false-positive - assert graph_has_legacy_ids([{"id": "setup", "source_file": "setup.py"}], root=".") is False + # sourceless / top-level file nodes don't false-positive + assert graph_has_legacy_ids([{"id": "setup", "source_file": "setup.py", "source_location": "L1"}], root=".") is False assert graph_has_legacy_ids([{"id": "x", "label": "y"}], root=".") is False + # package/dir-scoped SYMBOL ids (Go's _make_id(pkg_dir, name) -> "sub_thing") must + # NOT false-positive: not file-level (no L1), so ignored even though "sub_thing" + # coincides with the old file-stem form of pkg/sub/thing.go. + go_symbol = [{"id": "sub_thing", "source_file": "pkg/sub/thing.go", "type": "code", "source_location": "L3"}] + assert graph_has_legacy_ids(go_symbol, root=".") is False def test_semantic_rekey_relative_vs_absolute_source_file():