* Find ALL definitions matching a name, ranked, so codegraph_node can return * every overload instead of guessing one (the wrong guess → a Read). Keepers * rank before generated stubs (.pb.go etc.); stable within a group preserves * FTS order. Returns [] when nothing matches; a qualified lo
(cg: CodeGraph, symbol: string)
| 4316 | * bare name with no exact match falls back to the single top fuzzy result. |
| 4317 | */ |
| 4318 | private findSymbolMatches(cg: CodeGraph, symbol: string): Node[] { |
| 4319 | const isQualified = /[.\/]|::/.test(symbol); |
| 4320 | |
| 4321 | // For a bare name, enumerate EVERY exact-name definition via the direct index |
| 4322 | // (not FTS, which caps + ranks): tokio's `poll` has 50+ defs and the one the |
| 4323 | // caller wants (`Harness::poll` at harness.rs:153) ranks below any search cut, |
| 4324 | // so it could be neither rendered nor pinned by the file/line disambiguator — |
| 4325 | // and the agent Read it. With the full set, the multi-overload render + the |
| 4326 | // file/line filter can both reach it. |
| 4327 | if (!isQualified) { |
| 4328 | const exact = cg.getNodesByName(symbol); |
| 4329 | if (exact.length > 0) { |
| 4330 | return [...exact].sort((a, b) => (isGeneratedFile(a.filePath) ? 1 : 0) - (isGeneratedFile(b.filePath) ? 1 : 0)); |
| 4331 | } |
| 4332 | // No exact match — use the single top fuzzy result (e.g. a file basename). |
| 4333 | const fuzzy = cg.searchNodes(symbol, { limit: 10 }); |
| 4334 | return fuzzy[0] ? [fuzzy[0].node] : []; |
| 4335 | } |
| 4336 | |
| 4337 | // Qualified lookup (`Session.request`, `stage_apply::run`): FTS + matchesSymbol. |
| 4338 | const limit = 50; |
| 4339 | let results = cg.searchNodes(symbol, { limit }); |
| 4340 | |
| 4341 | // FTS strips colons, so `stage_apply::run` searches the literal |
| 4342 | // `stage_applyrun` and finds nothing. Re-search by the bare last part and |
| 4343 | // let `matchesSymbol` filter by qualifier. |
| 4344 | if (isQualified && results.length === 0) { |
| 4345 | const tail = lastQualifierPart(symbol); |
| 4346 | if (tail && tail !== symbol) results = cg.searchNodes(tail, { limit }); |
| 4347 | } |
| 4348 | |
| 4349 | if (results.length === 0) return []; |
| 4350 | |
| 4351 | const exactMatches = results.filter((r) => this.matchesSymbol(r.node, symbol)); |
| 4352 | if (exactMatches.length === 0) { |
| 4353 | // No exact match — a qualified lookup must not fall back to a fuzzy file |
| 4354 | // hit (#173); a bare name may use the single top fuzzy result. |
| 4355 | return isQualified ? [] : results[0] ? [results[0].node] : []; |
| 4356 | } |
| 4357 | |
| 4358 | // Down-rank generated files (.pb.go, .pulsar.go, _grpc.pb.go, …) so a flow |
| 4359 | // query prefers the keeper implementation over the protobuf-generated stub. |
| 4360 | return [...exactMatches] |
| 4361 | .sort((a, b) => (isGeneratedFile(a.node.filePath) ? 1 : 0) - (isGeneratedFile(b.node.filePath) ? 1 : 0)) |
| 4362 | .map((r) => r.node); |
| 4363 | } |
| 4364 | |
| 4365 | /** |
| 4366 | * Find ALL symbols matching a name. Used by callers/callees/impact to aggregate |
no test coverage detected