* 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)
| 4682 | * bare name with no exact match falls back to the single top fuzzy result. |
| 4683 | */ |
| 4684 | private findSymbolMatches(cg: CodeGraph, symbol: string): Node[] { |
| 4685 | const isQualified = /[.\/]|::/.test(symbol); |
| 4686 | |
| 4687 | // For a bare name, enumerate EVERY exact-name definition via the direct index |
| 4688 | // (not FTS, which caps + ranks): tokio's `poll` has 50+ defs and the one the |
| 4689 | // caller wants (`Harness::poll` at harness.rs:153) ranks below any search cut, |
| 4690 | // so it could be neither rendered nor pinned by the file/line disambiguator — |
| 4691 | // and the agent Read it. With the full set, the multi-overload render + the |
| 4692 | // file/line filter can both reach it. |
| 4693 | if (!isQualified) { |
| 4694 | const exact = cg.getNodesByName(symbol); |
| 4695 | if (exact.length > 0) { |
| 4696 | return [...exact].sort((a, b) => (isGeneratedFile(a.filePath) ? 1 : 0) - (isGeneratedFile(b.filePath) ? 1 : 0)); |
| 4697 | } |
| 4698 | // No exact match — use the single top fuzzy result (e.g. a file basename). |
| 4699 | const fuzzy = cg.searchNodes(symbol, { limit: 10 }); |
| 4700 | return fuzzy[0] ? [fuzzy[0].node] : []; |
| 4701 | } |
| 4702 | |
| 4703 | // Qualified lookup (`Session.request`, `stage_apply::run`): FTS + matchesSymbol. |
| 4704 | const limit = 50; |
| 4705 | let results = cg.searchNodes(symbol, { limit }); |
| 4706 | |
| 4707 | // FTS strips colons, so `stage_apply::run` searches the literal |
| 4708 | // `stage_applyrun` and finds nothing. Re-search by the bare last part and |
| 4709 | // let `matchesSymbol` filter by qualifier. |
| 4710 | if (isQualified && results.length === 0) { |
| 4711 | const tail = lastQualifierPart(symbol); |
| 4712 | if (tail && tail !== symbol) results = cg.searchNodes(tail, { limit }); |
| 4713 | } |
| 4714 | |
| 4715 | if (results.length === 0) return []; |
| 4716 | |
| 4717 | const exactMatches = results.filter((r) => this.matchesSymbol(r.node, symbol)); |
| 4718 | if (exactMatches.length === 0) { |
| 4719 | // No exact match — a qualified lookup must not fall back to a fuzzy file |
| 4720 | // hit (#173); a bare name may use the single top fuzzy result. |
| 4721 | return isQualified ? [] : results[0] ? [results[0].node] : []; |
| 4722 | } |
| 4723 | |
| 4724 | // Down-rank generated files (.pb.go, .pulsar.go, _grpc.pb.go, …) so a flow |
| 4725 | // query prefers the keeper implementation over the protobuf-generated stub. |
| 4726 | return [...exactMatches] |
| 4727 | .sort((a, b) => (isGeneratedFile(a.node.filePath) ? 1 : 0) - (isGeneratedFile(b.node.filePath) ? 1 : 0)) |
| 4728 | .map((r) => r.node); |
| 4729 | } |
| 4730 | |
| 4731 | /** |
| 4732 | * Find ALL symbols matching a name. Used by callers/callees/impact to aggregate |
no test coverage detected