* Build a URL → handler manifest from the index. Each route node's * `references` edge points at the function/method that handles the * request. We join them in one pass; the agent gets the canonical * routing answer ("POST /users/login → AuthController#login") without * having to parse
(limit: number = 40)
| 633 | * mapping AND the handler implementations. |
| 634 | */ |
| 635 | getRoutingManifest(limit: number = 40): { |
| 636 | entries: Array<{ url: string; handler: string; handlerFile: string; handlerLine: number; handlerKind: string }>; |
| 637 | topHandlerFile: string | null; |
| 638 | topHandlerFileCount: number; |
| 639 | totalRoutes: number; |
| 640 | } | null { |
| 641 | if (!this.stmts.getRoutingManifest) { |
| 642 | // Edge kind varies across framework resolvers: Spring/Rails/ |
| 643 | // Laravel/Drupal emit `references`, Express emits `calls`. Accept |
| 644 | // both — the semantic is the same (route → its handler). |
| 645 | this.stmts.getRoutingManifest = this.db.prepare(` |
| 646 | SELECT |
| 647 | r.name AS url, |
| 648 | h.name AS handler, |
| 649 | h.file_path AS handler_file, |
| 650 | h.start_line AS handler_line, |
| 651 | h.kind AS handler_kind |
| 652 | FROM nodes r |
| 653 | JOIN edges e ON e.source = r.id |
| 654 | JOIN nodes h ON e.target = h.id |
| 655 | WHERE r.kind = 'route' |
| 656 | AND e.kind IN ('references', 'calls') |
| 657 | AND h.kind IN ('function', 'method', 'class') |
| 658 | ORDER BY r.file_path, r.start_line |
| 659 | LIMIT ? |
| 660 | `); |
| 661 | } |
| 662 | const rows = this.stmts.getRoutingManifest.all(limit) as Array<{ |
| 663 | url: string; handler: string; handler_file: string; handler_line: number; handler_kind: string; |
| 664 | }>; |
| 665 | // Drop test/generated handlers — same hygiene as elsewhere. |
| 666 | const filtered = rows.filter(r => !isLowValueFile(r.handler_file)); |
| 667 | if (filtered.length < 3) return null; |
| 668 | // Identify the file holding the most handlers (the "primary handler file"). |
| 669 | const fileCounts = new Map<string, number>(); |
| 670 | for (const r of filtered) { |
| 671 | fileCounts.set(r.handler_file, (fileCounts.get(r.handler_file) ?? 0) + 1); |
| 672 | } |
| 673 | let topHandlerFile: string | null = null; |
| 674 | let topHandlerFileCount = 0; |
| 675 | for (const [file, count] of fileCounts) { |
| 676 | if (count > topHandlerFileCount) { |
| 677 | topHandlerFile = file; |
| 678 | topHandlerFileCount = count; |
| 679 | } |
| 680 | } |
| 681 | return { |
| 682 | entries: filtered.map(r => ({ |
| 683 | url: r.url, |
| 684 | handler: r.handler, |
| 685 | handlerFile: r.handler_file, |
| 686 | handlerLine: r.handler_line, |
| 687 | handlerKind: r.handler_kind, |
| 688 | })), |
| 689 | topHandlerFile, |
| 690 | topHandlerFileCount, |
| 691 | totalRoutes: filtered.length, |
| 692 | }; |
nothing calls this directly
no test coverage detected