* Graph-derived prompt matching for the front-load hook's MEDIUM tier: * which indexed symbols do these prose words name? "state machine des * commandes" → `OrderStateMachine`, in any human language whose technical * nouns are Latin script — no keyword list involved. * * Precision com
(words: string[], limit: number = 6)
| 1320 | * returned symbol is guaranteed to exist right now. |
| 1321 | */ |
| 1322 | getSegmentMatches(words: string[], limit: number = 6): SegmentMatch[] { |
| 1323 | if (words.length === 0) return []; |
| 1324 | // Variant → original word (plural folding), for coverage accounting. |
| 1325 | const variantToWord = new Map<string, string>(); |
| 1326 | for (const word of words) { |
| 1327 | for (const variant of segmentLookupVariants(word)) { |
| 1328 | if (!variantToWord.has(variant)) variantToWord.set(variant, word); |
| 1329 | } |
| 1330 | } |
| 1331 | const variants = [...variantToWord.keys()]; |
| 1332 | |
| 1333 | // Tier A: co-occurrence. The SQL folds variants back to their original |
| 1334 | // word (#1146), so minWords=2 means two distinct PROMPT WORDS — a name |
| 1335 | // matching both `service` and `services` can't tie with (or crowd past |
| 1336 | // the LIMIT) a genuine two-word match. The JS re-check below recomputes |
| 1337 | // the fold from live segments as the honesty layer. |
| 1338 | const variantPairs = [...variantToWord.entries()].map(([segment, word]) => ({ segment, word })); |
| 1339 | const candidates: Array<{ name: string; matchedWords: Set<string> }> = []; |
| 1340 | for (const hit of this.queries.getSegmentCoOccurrence(variantPairs, 2, 24)) { |
| 1341 | const matched = this.wordsMatchingName(hit.name, variantToWord); |
| 1342 | if (matched.size >= 2) candidates.push({ name: hit.name, matchedWords: matched }); |
| 1343 | } |
| 1344 | |
| 1345 | // Tier B: single rare word. Only when co-occurrence found nothing — a |
| 1346 | // co-occurring name is categorically stronger evidence — and under |
| 1347 | // stricter rules, because one word is thin: the word must be ≥5 chars |
| 1348 | // (measured FPs: "this", "typo"); the segment must appear in AT LEAST TWO |
| 1349 | // names (a concept the codebase is about clusters across names — |
| 1350 | // CheckoutService/CheckoutController — while a prose coincidence is a |
| 1351 | // singleton: measured FP "deploy to PRODUCTION" → the one name |
| 1352 | // matchesNonProductionDir); and the candidate name must have ≥2 segments |
| 1353 | // (a bare common verb matching a bare function name — "write" → `write` — |
| 1354 | // is prose coincidence, not the user naming a symbol). |
| 1355 | if (candidates.length === 0) { |
| 1356 | const singleWordVariants = variants.filter((v) => variantToWord.get(v)!.length >= 5); |
| 1357 | const counts = this.queries.getSegmentNameCounts(singleWordVariants); |
| 1358 | const rare = [...counts.entries()] |
| 1359 | .filter(([, n]) => n >= 2 && n <= CodeGraph.SEGMENT_RARITY_CEILING) |
| 1360 | .sort((a, b) => a[1] - b[1]) |
| 1361 | .slice(0, 2); |
| 1362 | for (const [variant] of rare) { |
| 1363 | const word = variantToWord.get(variant)!; |
| 1364 | for (const name of this.queries.getNamesForSegment(variant, 12)) { |
| 1365 | if (splitIdentifierSegments(name).length < 2) continue; |
| 1366 | candidates.push({ name, matchedWords: new Set([word]) }); |
| 1367 | } |
| 1368 | } |
| 1369 | } |
| 1370 | |
| 1371 | // Verify against nodes (the honesty gate) and pick a representative |
| 1372 | // definition per name. A name whose only nodes are file/import kind has |
| 1373 | // no real definition to point at — surfacing the import statement instead |
| 1374 | // reads as a matched symbol but isn't one (#1144) — so it's skipped, the |
| 1375 | // same way an orphaned vocab row is. (Import names no longer enter the |
| 1376 | // vocab at write time, but rows written before that exclusion persist |
| 1377 | // until the next full index.) |
| 1378 | const out: SegmentMatch[] = []; |
| 1379 | const seen = new Set<string>(); |
no test coverage detected