fix(retrieval): multi-hump field-name queries reach their definers (#1319)

Three compounding defects (#1196) made a query bag of object-literal
keys (`profileInfo isTrialEligible quotaInfo billingMethod`) return
unrelated results while the defining files never surfaced:

1. Step 5b title-cased interior humps (profileInfo -> Profileinfo) and
   then compared case-SENSITIVELY, dropping every row SQLite's
   case-insensitive LIKE had just recovered. The hump lookup is now
   case-insensitive with an explicit uppercase-at-match requirement.
2. Step 5b/5c's kind whitelist held only type-like kinds — dead code on
   method-centric codebases. Callable kinds (function/method/component)
   are fetched as a SEPARATE LIKE batch so hot single-word terms can't
   crowd classes out of the length-ordered 200-row batch.
3. explore's named-symbol seeding was exact-name only; a field token
   seeded nothing. A camelCase token with ZERO exact defs now seeds its
   camel-infix definers (callables, hump-boundary or prefix, shortest
   first, capped at 3) — bare lowercase words keep the #1252 stopword
   guard untouched.

The reporter's acceptance query is a pinned e2e test (definer files
present, exact-name seeding unaffected). excalidraw probe: the
canonical flow query (mutateElement renderStaticScene) is byte-
identical; NL queries shift toward more-central callables
(useUIAppState/getDefaultAppState over observer periphery).

Fixes #1196

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 16:11:24 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent a5a8942d1c
commit 1de7e8f8b5
5 changed files with 184 additions and 11 deletions
+25
View File
@@ -2653,6 +2653,31 @@ export class ToolHandler {
let cands = raw
.filter((n) => CALLABLE.has(n.kind) && !isTestPath(n.filePath))
.sort((a, b) => (bodyLines(b) > 1 ? 1 : 0) - (bodyLines(a) > 1 ? 1 : 0) || bodyLines(b) - bodyLines(a));
// Field-name seeding fallback (#1196): a camelCase token that names NO
// definition of its own is usually an object-literal key / API field
// (`profileInfo`) — no node exists, so it contributed zero seeds and
// the files that DEFINE it (`getProfileInfoV2` in profileController)
// never surfaced. Seed its camel-infix definers instead: callables
// whose name contains the token at a hump boundary or as a prefix.
// Exact-empty + camel-shaped only (bare words keep the NL-stopword
// guard below), shortest-first, capped so a hot infix can't flood.
if (cands.length === 0 && !isQual && /[a-z][A-Z]/.test(t)) {
const lcToken = t.toLowerCase();
cands = cg
.getNodesByNameSubstring(t, {
kinds: ['function', 'method', 'component'],
limit: 60,
})
.filter((n) => CALLABLE.has(n.kind) && !isTestPath(n.filePath))
.filter((n) => {
const idx = n.name.toLowerCase().indexOf(lcToken);
if (idx < 0) return false;
if (idx === 0) return n.name.length > t.length; // prefix definer
return /[A-Z]/.test(n.name.charAt(idx)); // camel-hump boundary
})
.sort((a, b) => a.name.length - b.name.length)
.slice(0, 3);
}
// Bare lowercase words only seed defs their query-siblings corroborate
// (see the NL-stopword guard above). Filtering CANDS (not picks) applies
// the guard uniformly to both branches below, including the >3-def