Repository navigation
fix: prevent silent node loss from qualified_name collisions - #586
CodeBlackwell wants to merge 2 commits into
Conversation
qualified_name is the graph's stable identity key, built as file::name or file::parent.name. It is not unique per symbol: same-named symbols in one file (e.g. methods of different object literals, or repeated generated helpers) produce the same key. upsert_node inserts with ON CONFLICT(qualified_name) DO UPDATE, so colliding nodes collapsed last-writer-wins and earlier definitions were silently dropped. Deduplicate per file in the batch store functions, which already hold the whole file's node list. The first occurrence keeps its bare key so existing edges (which reference the same parser-computed key) still resolve to it; later collisions are suffixed with :L<line_start>, which is always unique. Unique nodes keep byte-identical keys, so embeddings and incremental updates are unaffected.
Surfaces same-file qualified_name collisions that were previously invisible. Adds GraphStore.find_disambiguated_nodes() (matches the :L<line> marker the batch store appends), threads a disambiguated_nodes list into the full_build and incremental_update results, logs it, and prints a one-line summary after build/update. Counts are unchanged: now that collisions are kept rather than dropped, the reported node total and the stored COUNT(*) already agree. Adds tests/test_collisions.py covering no-drop, the bare-key-first invariant, edge-resolution safety, and the disambiguation report.
a552b0b to
99b0a41
Compare
|
Quick status update: this fix is still current and independent of #614 (the CSS/SCSS feature PR) — the two don't overlap, so this one stays open on its own merits. The branch sits directly on today's main tip and GitHub reports it mergeable with no conflicts. Happy to adjust anything if you'd like changes. |
|
Thanks—this prevents colliding rows from being lost, but ambiguous calls are still attached to the first definition. That can produce a confident but incorrect edge. The Please define a stable identity or migration/rebuild strategy, then make calls resolve to the correct duplicate when evidence exists—or remain explicitly ambiguous when it does not. Please add end-to-end tests for callers of each overload/duplicate and an incremental edit that shifts line numbers. |
|
Thanks for the contribution. I rechecked exact head Adding Issue #585 remains open for a stable parser/resolver-level identity design. |
…iew of #715) A call with nothing to choose between same-file duplicates no longer binds to the first one, the reason #586 was refused. Like an unbound target in #1171 it keeps the plain name, lists every duplicate, and says AMBIGUOUS 0.4, so callers_of on either claims nothing. Line scope and recursion still bind. A settle pass after each store fixes calls written before or after the duplicates and binds them again once one definition is left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew of tirth8205#715) Strict xfail. Every unevidenced call still binds to the first duplicate, the objection tirth8205#586 was closed over, so find_dead_code lists the called area(int, int) overload that staging's collapsed node kept alive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #585.
Problem
qualified_nameis the graph's stable identity key, built asfile::nameorfile::parent.name. It is not unique per symbol: same-named symbols in one file (methods of different object literals, repeated generated helpers) produce the same key.upsert_nodeinserts withON CONFLICT(qualified_name) DO UPDATE, so colliding rows collapse last writer wins and earlier definitions are silently dropped. A build then reports more nodes than the database stores, and caller/callee analysis on those names points at a single merged node.Changes
Commit 1 — disambiguate colliding qualified_names. Deduplicate per file in
store_file_nodes_edgesandstore_file_batch, which already hold the whole file's node list. The first occurrence keeps its bare key so existing edges still resolve to it; later collisions are suffixed with:L<line_start>, which is always unique because two definitions cannot share a start line. Unique nodes keep byte identical keys, so embeddings and incremental updates are unaffected.Commit 2 — report disambiguated names. Adds
GraphStore.find_disambiguated_nodes(), adisambiguated_nodesfield in thefull_buildandincremental_updateresults, a log line, and a one line CLI summary after build/update. Counts are not rewritten: now that collisions are kept rather than dropped, the reported node total and the storedCOUNT(*)already agree.Verification
New
tests/test_collisions.pycovers: both same-named methods persist, three module scope duplicates persist, an edge to the bare key still resolves to the first definition, the disambiguation report, and that unique names keep bare keys. Full suite green (1389 passed), ruff and mypy clean.Caveat
A collided node's identity shifts if its own
line_startmoves. This affects only ambiguous duplicates, never the unique majority. Open to a different marker convention if you prefer (see #585).🤖 Generated with Claude Code