* 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)
| 4445 | * bare name with no exact match falls back to the single top fuzzy result. |
| 4446 | */ |
| 4447 | private findSymbolMatches(cg: CodeGraph, symbol: string): Node[] { |
| 4448 | const isQualified = /[.\/]|::/.test(symbol); |
| 4449 | |
| 4450 | // For a bare name, enumerate EVERY exact-name definition via the direct index |
| 4451 | // (not FTS, which caps + ranks): tokio's `poll` has 50+ defs and the one the |
| 4452 | // caller wants (`Harness::poll` at harness.rs:153) ranks below any search cut, |
| 4453 | // so it could be neither rendered nor pinned by the file/line disambiguator — |
| 4454 | // and the agent Read it. With the full set, the multi-overload render + the |
| 4455 | // file/line filter can both reach it. |
| 4456 | if (!isQualified) { |
| 4457 | const exact = cg.getNodesByName(symbol); |
| 4458 | if (exact.length > 0) { |
| 4459 | return [...exact].sort((a, b) => (isGeneratedFile(a.filePath) ? 1 : 0) - (isGeneratedFile(b.filePath) ? 1 : 0)); |
| 4460 | } |
| 4461 | // No exact match — use the single top fuzzy result (e.g. a file basename). |
| 4462 | const fuzzy = cg.searchNodes(symbol, { limit: 10 }); |
| 4463 | return fuzzy[0] ? [fuzzy[0].node] : []; |
| 4464 | } |
| 4465 | |
| 4466 | // Qualified lookup (`Session.request`, `stage_apply::run`): FTS + matchesSymbol. |
| 4467 | const limit = 50; |
| 4468 | let results = cg.searchNodes(symbol, { limit }); |
| 4469 | |
| 4470 | // FTS strips colons, so `stage_apply::run` searches the literal |
| 4471 | // `stage_applyrun` and finds nothing. Re-search by the bare last part and |
| 4472 | // let `matchesSymbol` filter by qualifier. |
| 4473 | if (isQualified && results.length === 0) { |
| 4474 | const tail = lastQualifierPart(symbol); |
| 4475 | if (tail && tail !== symbol) results = cg.searchNodes(tail, { limit }); |
| 4476 | } |
| 4477 | |
| 4478 | if (results.length === 0) return []; |
| 4479 | |
| 4480 | const exactMatches = results.filter((r) => this.matchesSymbol(r.node, symbol)); |
| 4481 | if (exactMatches.length === 0) { |
| 4482 | // No exact match — a qualified lookup must not fall back to a fuzzy file |
| 4483 | // hit (#173); a bare name may use the single top fuzzy result. |
| 4484 | return isQualified ? [] : results[0] ? [results[0].node] : []; |
| 4485 | } |
| 4486 | |
| 4487 | // Down-rank generated files (.pb.go, .pulsar.go, _grpc.pb.go, …) so a flow |
| 4488 | // query prefers the keeper implementation over the protobuf-generated stub. |
| 4489 | return [...exactMatches] |
| 4490 | .sort((a, b) => (isGeneratedFile(a.node.filePath) ? 1 : 0) - (isGeneratedFile(b.node.filePath) ? 1 : 0)) |
| 4491 | .map((r) => r.node); |
| 4492 | } |
| 4493 | |
| 4494 | /** |
| 4495 | * Find ALL symbols matching a name. Used by callers/callees/impact to aggregate |
no test coverage detected