From 73710d33ce0986401f316cc1532cbaa33caed1f6 Mon Sep 17 00:00:00 2001 From: safishamsi Date: Sun, 28 Jun 2026 17:52:34 +0100 Subject: [PATCH] fix(ids): legacy-id detector only inspects file-level nodes (no Go false-positive) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The graph_has_legacy_ids nudge flagged fresh v9 graphs that contain Go code, because the Go extractor scopes a type node by package directory (_make_id(pkg_dir, name) -> "sub_thing"), which coincides with the OLD one-parent file-stem form of pkg/sub/thing.go even though it's a current, by-design package-scoped id (methods on a type across files in a package share one node). The detector now inspects only file-level nodes (source_location "L1"), whose id is unambiguously the file stem and which the migration actually re-keys. This still catches a genuine pre-#1504 graph (its file nodes carry the old stem) while leaving package/dir-scoped symbol ids alone. Verified end-to-end: a fresh Go graph no longer triggers the rebuild nudge; a real legacy graph still does. The migration itself was already correct — file nodes and Python/AST symbols migrate to full-path ids; Go's package-scoped type ids are intentional and unaffected. This only fixes the over-eager warning. Co-Authored-By: Claude Opus 4.8 (1M context) --- graphify/build.py | 11 ++++++++--- tests/test_build.py | 13 +++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) 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():