Skip to content

Commit 0a7a8aa

Browse files
Your Nameclaude
andcommitted
feat(resolver): WS0-WS6 context-intelligence upgrade (ambiguity groups, inheritance closure, non-destructive same-dir ranking)
Implements docs/plans/2026-08-18-context-intelligence-upgrade-plan.md's WS3-WS5 (WS0-WS2 shipped in earlier commits; WS6 evaluated and deferred, see below). Every workstream verified against the plan's own WS0 precision/false-confidence benchmark before and after, with full `cargo test --workspace` green throughout. WS3 -- record ambiguity groups instead of silently dropping them: `resolve_sites_to_edges` now returns `(Vec<CallEdge>, Vec<AmbiguityGroup>)`. A call site whose surviving candidate set exceeds MAX_CALLEE_CANDIDATES no longer vanishes with zero trace -- it's recorded in a new `ambiguity_groups` table (call_site_id, from_path, candidate_group_key, candidate_count, reason), persisted by both rebuild_graph and incremental_graph_update. `callers()` gains `unresolved_group_count` plus a caveat that fires even when direct_count > 0; `reference_impact()` gains the sibling `unresolved_many_count`. WS4 -- inheritance closure as a real resolution mechanism: `build_resolution_context` now builds a depth-level-aware inheritance/interface closure from `type_relations` (extends/implements), gated to `resolved`-confidence rows only -- never `textual`, per the plan's own hard, non-negotiable rule. `resolve_cross_file_type_relations` was moved to run before `build_resolution_context` in both graph-rebuild paths (previously ran after resolve_sites_to_edges, making the closure exactly one pass stale). resolve_via_inheritance_closure walks levels nearest-first and only trusts a singleton match at the nearest declaring level, refusing to pick between two same-depth ancestors that both declare the same method name (e.g. `interface Mixed extends IA, IB`). WS5 -- same_dir becomes a non-destructive ranker (C/C++ only): `call_edges` gains `candidate_rank` (0 = preferred/same-dir, 1+ = alternate). Found during implementation that the plan's own root-cause text was wrong for Go/Java: both have real, compiler-enforced package scoping (confirmed by two existing regression tests breaking under the naive all-four-languages interpretation), so only C/C++ -- which have no package/namespace-to-directory correspondence at all -- get the non-destructive treatment; Go/Java keep the pre-WS5 hard filter. `callers()`/`callees()` sort `ambiguous` by `candidate_rank`. WS6 -- evaluated against its own WS0-evidence gate, not met: the corpus's one real false-confidence case (fixture I / D8) is a different defect (overlay/static-layer reconciliation) than what WS6 targets (ambiguous-candidate disambiguation via StackGraph's definition_symbol). Documented as a reasoned defer in the plan doc; recommends a new WS7 for D8 instead of building WS6 speculatively. Net WS0 benchmark result across the whole sequence: call_recall 0.75 -> 0.875 (WS5's fixture E recovering a previously-silently-dropped true target), false_confidence_rate/false_confident_site_rate unchanged (0.25 / 0.125) -- a clean recall gain with no precision cost. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 54a9f89 commit 0a7a8aa

102 files changed

Lines changed: 3074 additions & 98 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ __pycache__/
6363
# resolution benchmark's cloned OSS corpora — external repos, not ours to commit
6464
benchmarks/resolution/corpus/
6565

66+
# WS0 resolution-precision fixtures are hand-authored and committed, but each
67+
# fixture's own transient index (rebuilt fresh every run) is not
68+
benchmarks/resolution_precision/fixtures/*/.calm/
69+
6670
# OS / editor
6771
.DS_Store
6872
Thumbs.db

benchmarks/resolution_precision/README.md

Lines changed: 401 additions & 0 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from lib import bar
2+
3+
4+
def use():
5+
return bar()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def bar():
2+
return "decoy_01"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def bar():
2+
return "decoy_02"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def bar():
2+
return "decoy_03"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def bar():
2+
return "decoy_04"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def bar():
2+
return "decoy_05"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def bar():
2+
return "decoy_06"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def bar():
2+
return "decoy_07"

0 commit comments

Comments
 (0)