Skip to content

fix(resolve,blast): demote unverified cross-scope guesses; seed diff-blast by changed line range#116

Merged
luuuc merged 1 commit into
mainfrom
fix/resolver-cross-type-and-diff-line-range
May 31, 2026
Merged

fix(resolve,blast): demote unverified cross-scope guesses; seed diff-blast by changed line range#116
luuuc merged 1 commit into
mainfrom
fix/resolver-cross-type-and-diff-line-range

Conversation

@luuuc
Copy link
Copy Markdown
Owner

@luuuc luuuc commented May 31, 2026

Problem

Two related defects undermined trust in calls edges and diff-blast on a real Rails codebase:

  1. Phantom calls edges. The resolver's unqualified-name fallback granted caller-grade confidence to leaf-only matches whose qualifier could not be verified. An instance call on an external gem type (Stripe::StripeError#message) missed exact lookup and bound its bare leaf to a coincidental same-named test method at full 0.7; bare receiver-unknown calls (x.body) bound at 0.5; and a dispatch-kind contradiction (instance call, singleton-only candidate) failed open instead of demoting. These plausible-looking-but-wrong edges meant outbound calls could not be taken at face value.

  2. Over-broad diff-blast. Seeding selected every symbol in any touched file, so a one-line edit to a routes file with hundreds of helpers seeded them all — inflating blast radius by an order of magnitude on hub-file commits.

Summary

Make resolver confidence track verifiability, and seed diff-blast from changed line ranges instead of whole files.

Changes

  • internal/resolve — generalize the existing ::-only cross-namespace demotion into one rule: a qualified target that resolves only by its leaf is demoted to ConfidenceNameCollision (below blast's floor, still counted for dead-code liveness) unless the qualifier is verifiable — the receiver type is an indexed symbol (so an inherited/reopened method is plausible) and the dispatch kind agrees. Bare targets keep base confidence only when the extractor was confident; a bare emit at ConfidenceUnresolved is a receiver-unknown guess and is demoted. filterByReceiver now reports when every candidate's kind contradicts the call so the resolver demotes rather than fails open.
  • internal/cli + internal/mcpserver — replace file-granular SymbolsInFiles with GitDiffHunks (git diff -U0) + SymbolsInChangedLines, which seed only symbols whose line span overlaps a changed hunk. Wired into both the CLI and MCP diff paths; removed the now-unused file-granular helpers.

Architecture Highlights

  • Inheritance/reopening is preserved: an indexed receiver type keeps base confidence, so Child#mParent#m still resolves. Go/Python top-level bare calls (emitted at 1.0) are untouched.
  • The git invocation keeps its hardening (--end-of-options, LookPath, preserved stderr). Pure deletions and /dev/null targets contribute no ranges.

Verification (real Rails app)

  • Production→test calls edges at confidence ≥0.5: 689 → 465 (the StripeClient phantoms are gone; the remainder are framework-inherited and exact test-stub matches that are out of scope for this fix).
  • Diff-blast on a hub-file commit: 939 → 82 modified symbols.

Test Plan

  • Resolver: external-type demotion, inherited-method preserved, receiver-kind contradiction, bare-unresolved demotion, view-source carve-out
  • gitdiff: hunk parsing (additions, deletions, multi-file, renames, /dev/null), overlap selection, chunk boundary, scan/query errors
  • Coverage ≥92% line & per-function on modified files (resolver.go 98.2%, gitdiff.go 96.6%)
  • make ci fully green (build, test, lint 0 issues)
  • sense binary builds cleanly

…blast by changed line range

Two root causes behind phantom calls edges and over-broad diff-blast.

Resolver: the unqualified-name fallback granted caller-grade confidence
to leaf-only matches whose qualifier could not be verified. An instance
call on an external gem type (`Stripe::StripeError#message`) missed exact
lookup and bound its bare leaf to a coincidental same-named test method
at full 0.7; bare receiver-unknown calls (`x.body`) bound at 0.5; and a
dispatch-kind contradiction (instance call, singleton-only candidate)
failed open instead of demoting. Generalize the existing `::`-only
cross-namespace demotion into one rule: a qualified target that resolves
only by its leaf is demoted to ConfidenceNameCollision (below blast's
floor, still counted for dead-code liveness) unless the qualifier is
verifiable — the receiver type is an indexed symbol (so an inherited or
reopened method is plausible) and the dispatch kind agrees. Bare targets
keep base confidence only when the extractor was confident; a bare emit
at ConfidenceUnresolved is a receiver-unknown guess and is demoted.
filterByReceiver now reports when every candidate's kind contradicted the
call so the resolver can demote rather than fail open.

Diff-blast: SymbolsInFiles seeded every symbol in any touched file, so a
one-line edit to a routes file with hundreds of helpers seeded them all.
Replace with GitDiffHunks (git diff -U0) + SymbolsInChangedLines, which
seed only symbols whose line span overlaps a changed hunk. Wire into both
the CLI and MCP diff paths; remove the now-unused file-granular helpers.

Verified on a real Rails app: external-type and bare phantom edges drop
from the >=0.5 tier (production->test calls edges 689 -> 465, with the
remainder being framework-inherited and exact test-stub matches outside
this fix's scope), and diff-blast on a hub-file commit falls from 939 to
82 modified symbols.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 31, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@luuuc luuuc self-assigned this May 31, 2026
@luuuc luuuc merged commit d17017b into main May 31, 2026
6 checks passed
@luuuc luuuc deleted the fix/resolver-cross-type-and-diff-line-range branch May 31, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant