gitnexus 1.6.8-rc.2 → 1.6.8-rc.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +20 -0
  2. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  3. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  4. package/dist/_shared/scope-resolution/symbol-definition.d.ts +4 -0
  5. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  6. package/dist/cli/ai-context.js +1 -0
  7. package/dist/cli/analyze-config.js +40 -0
  8. package/dist/cli/analyze.d.ts +22 -0
  9. package/dist/cli/analyze.js +36 -6
  10. package/dist/cli/clean.d.ts +1 -0
  11. package/dist/cli/clean.js +43 -1
  12. package/dist/cli/eval-server.js +43 -0
  13. package/dist/cli/help-i18n.js +11 -0
  14. package/dist/cli/i18n/en.d.ts +13 -0
  15. package/dist/cli/i18n/en.js +13 -0
  16. package/dist/cli/i18n/resources.d.ts +26 -0
  17. package/dist/cli/i18n/zh-CN.d.ts +13 -0
  18. package/dist/cli/i18n/zh-CN.js +13 -0
  19. package/dist/cli/index.js +19 -0
  20. package/dist/cli/list.js +12 -0
  21. package/dist/cli/optional-grammars.d.ts +6 -8
  22. package/dist/cli/optional-grammars.js +8 -11
  23. package/dist/cli/skill-gen.js +1 -0
  24. package/dist/cli/status.js +26 -5
  25. package/dist/cli/tool.d.ts +12 -1
  26. package/dist/cli/tool.js +41 -1
  27. package/dist/core/embeddings/embedder.js +4 -0
  28. package/dist/core/embeddings/embedding-pipeline.js +27 -16
  29. package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
  30. package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
  31. package/dist/core/graph/import-cycles.d.ts +10 -0
  32. package/dist/core/graph/import-cycles.js +103 -0
  33. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  34. package/dist/core/group/extractors/http-patterns/java.js +3 -50
  35. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  36. package/dist/core/group/extractors/include-extractor.js +7 -7
  37. package/dist/core/ingestion/cfg/cfg-builder.d.ts +64 -0
  38. package/dist/core/ingestion/cfg/cfg-builder.js +129 -0
  39. package/dist/core/ingestion/cfg/collect.d.ts +30 -0
  40. package/dist/core/ingestion/cfg/collect.js +34 -0
  41. package/dist/core/ingestion/cfg/control-flow-context.d.ts +97 -0
  42. package/dist/core/ingestion/cfg/control-flow-context.js +113 -0
  43. package/dist/core/ingestion/cfg/emit.d.ts +144 -0
  44. package/dist/core/ingestion/cfg/emit.js +315 -0
  45. package/dist/core/ingestion/cfg/reaching-defs.d.ts +90 -0
  46. package/dist/core/ingestion/cfg/reaching-defs.js +364 -0
  47. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  48. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  49. package/dist/core/ingestion/cfg/types.d.ts +231 -0
  50. package/dist/core/ingestion/cfg/types.js +13 -0
  51. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +144 -0
  52. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1032 -0
  53. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  54. package/dist/core/ingestion/cfg/visitors/typescript.js +584 -0
  55. package/dist/core/ingestion/language-provider.d.ts +36 -2
  56. package/dist/core/ingestion/languages/c-cpp.js +11 -4
  57. package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
  58. package/dist/core/ingestion/languages/cpp/captures.js +24 -1
  59. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  60. package/dist/core/ingestion/languages/java.js +3 -0
  61. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  62. package/dist/core/ingestion/languages/kotlin.js +5 -1
  63. package/dist/core/ingestion/languages/typescript.js +5 -0
  64. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  65. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  66. package/dist/core/ingestion/method-types.d.ts +2 -0
  67. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  68. package/dist/core/ingestion/model/symbol-table.js +1 -0
  69. package/dist/core/ingestion/parsing-processor.js +22 -0
  70. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  71. package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
  72. package/dist/core/ingestion/pipeline.d.ts +57 -0
  73. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
  74. package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
  75. package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
  76. package/dist/core/ingestion/route-extractors/spring.js +136 -0
  77. package/dist/core/ingestion/scope-extractor.js +3 -0
  78. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
  79. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
  80. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
  81. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  82. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +21 -0
  83. package/dist/core/ingestion/scope-resolution/pipeline/run.js +207 -0
  84. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  85. package/dist/core/ingestion/taint/emit.d.ts +124 -0
  86. package/dist/core/ingestion/taint/emit.js +204 -0
  87. package/dist/core/ingestion/taint/match.d.ts +153 -0
  88. package/dist/core/ingestion/taint/match.js +278 -0
  89. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  90. package/dist/core/ingestion/taint/path-codec.js +190 -0
  91. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  92. package/dist/core/ingestion/taint/propagate.js +664 -0
  93. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  94. package/dist/core/ingestion/taint/site-safety.js +98 -0
  95. package/dist/core/ingestion/taint/source-sink-config.d.ts +94 -23
  96. package/dist/core/ingestion/taint/source-sink-config.js +11 -11
  97. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  98. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  99. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  100. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  101. package/dist/core/ingestion/utils/method-props.js +1 -0
  102. package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
  103. package/dist/core/ingestion/workers/clone-safety.js +465 -0
  104. package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
  105. package/dist/core/ingestion/workers/parse-worker.js +70 -53
  106. package/dist/core/ingestion/workers/post-result.d.ts +22 -0
  107. package/dist/core/ingestion/workers/post-result.js +87 -0
  108. package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
  109. package/dist/core/ingestion/workers/result-merge.js +43 -0
  110. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
  111. package/dist/core/ingestion/workers/worker-pool.js +33 -16
  112. package/dist/core/lbug/lbug-adapter.d.ts +19 -0
  113. package/dist/core/lbug/lbug-adapter.js +56 -1
  114. package/dist/core/run-analyze.d.ts +87 -0
  115. package/dist/core/run-analyze.js +280 -25
  116. package/dist/core/tree-sitter/parser-loader.js +5 -4
  117. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  118. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  119. package/dist/mcp/core/embedder.js +4 -0
  120. package/dist/mcp/local/local-backend.d.ts +86 -2
  121. package/dist/mcp/local/local-backend.js +657 -27
  122. package/dist/mcp/resources.js +1 -0
  123. package/dist/mcp/tools.d.ts +9 -0
  124. package/dist/mcp/tools.js +109 -0
  125. package/dist/server/analyze-launch.d.ts +29 -0
  126. package/dist/server/analyze-launch.js +137 -0
  127. package/dist/server/analyze-upload.d.ts +33 -0
  128. package/dist/server/analyze-upload.js +123 -0
  129. package/dist/server/analyze-worker-ipc.d.ts +58 -0
  130. package/dist/server/analyze-worker-ipc.js +16 -0
  131. package/dist/server/analyze-worker.d.ts +17 -1
  132. package/dist/server/analyze-worker.js +7 -1
  133. package/dist/server/api.js +46 -140
  134. package/dist/server/git-clone.d.ts +1 -0
  135. package/dist/server/git-clone.js +1 -1
  136. package/dist/server/middleware.d.ts +11 -0
  137. package/dist/server/middleware.js +27 -0
  138. package/dist/server/upload-ingest.d.ts +56 -0
  139. package/dist/server/upload-ingest.js +276 -0
  140. package/dist/server/upload-paths.d.ts +31 -0
  141. package/dist/server/upload-paths.js +51 -0
  142. package/dist/server/upload-sweep.d.ts +20 -0
  143. package/dist/server/upload-sweep.js +57 -0
  144. package/dist/storage/branch-index.d.ts +52 -0
  145. package/dist/storage/branch-index.js +65 -0
  146. package/dist/storage/git.d.ts +11 -0
  147. package/dist/storage/git.js +28 -0
  148. package/dist/storage/parse-cache.d.ts +22 -1
  149. package/dist/storage/parse-cache.js +32 -10
  150. package/dist/storage/repo-manager.d.ts +122 -10
  151. package/dist/storage/repo-manager.js +162 -21
  152. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +42 -8
  153. package/hooks/claude/gitnexus-hook.cjs +36 -8
  154. package/hooks/claude/hook-db-lock-probe.cjs +123 -2
  155. package/package.json +4 -2
  156. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  157. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  158. package/skills/gitnexus-guide.md +11 -0
  159. package/vendor/tree-sitter-c/package.json +1 -1
  160. package/vendor/tree-sitter-dart/package.json +1 -1
  161. package/vendor/tree-sitter-kotlin/package.json +1 -1
  162. package/vendor/tree-sitter-proto/package.json +1 -1
  163. package/vendor/tree-sitter-swift/package.json +1 -1
  164. package/web/assets/{agent-CKPMqImC.js → agent-ay4LD70X.js} +1 -1
  165. package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
  166. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
  167. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
  168. package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
  169. package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
  170. package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
  171. package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
  172. package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
  173. package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
  174. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
  175. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
  176. package/web/assets/index-COMMmbxW.css +2 -0
  177. package/web/assets/{index-CG6q8eTs.js → index-Do8AE5yF.js} +85 -85
  178. package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
  179. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
  180. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
  181. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
  182. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
  183. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
  184. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
  185. package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
  186. package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
  187. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
  188. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
  189. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
  190. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
  191. package/web/index.html +2 -2
  192. package/scripts/materialize-vendor-grammars.cjs +0 -97
  193. package/web/assets/index-BKWA-m7o.css +0 -2
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Shared Spring route-annotation primitives.
3
+ *
4
+ * These are the low-level building blocks the two Spring route extractors —
5
+ * the ingestion-layer `route-extractors/spring.ts` (produces graph `Route`
6
+ * nodes) and the group-layer `group/extractors/http-patterns/java.ts`
7
+ * (produces cross-repo HTTP contracts) — would otherwise each maintain
8
+ * independently. Centralising the annotation→method map, the enclosing-class
9
+ * lookup, and the route-key filter keeps those semantics in one place so the
10
+ * two extractors can't drift apart.
11
+ *
12
+ * This module lives in `ingestion/` (the lower layer); the group layer imports
13
+ * from it, matching the existing `group → ingestion` dependency direction
14
+ * (e.g. `group/extractors/include-extractor.ts` already imports
15
+ * `ingestion/import-resolvers/utils.ts`). It MUST NOT import anything from
16
+ * `group/` to avoid a dependency cycle.
17
+ */
18
+ import type Parser from 'tree-sitter';
19
+ /**
20
+ * Spring shortcut method-annotation → HTTP verb.
21
+ *
22
+ * `@RequestMapping` is intentionally absent: on a method it carries no implicit
23
+ * verb (the verb lives in its `method = RequestMethod.X` attribute), and on a
24
+ * class it is a URL prefix rather than a route. Callers handle `@RequestMapping`
25
+ * separately.
26
+ */
27
+ export declare const METHOD_ANNOTATION_TO_HTTP: Record<string, string>;
28
+ /**
29
+ * A named annotation argument contributes a route only when its member key is
30
+ * `path` or `value`; a positional argument (no key node) always qualifies.
31
+ * Drops Spring's non-route string attributes (`produces`, `consumes`,
32
+ * `headers`, `name`, `params`) that would otherwise be mis-read as routes.
33
+ */
34
+ export declare function isRouteMemberKey(keyNode: Parser.SyntaxNode | undefined): boolean;
35
+ /**
36
+ * Find the nearest enclosing `class_declaration` ancestor for a node, or null
37
+ * if the node is top-level. Tree-sitter's `SyntaxNode.parent` walks one level
38
+ * at a time.
39
+ */
40
+ export declare function findEnclosingClass(node: Parser.SyntaxNode): Parser.SyntaxNode | null;
41
+ /**
42
+ * Strip enclosing quotes from a tree-sitter string-literal node's text.
43
+ * Handles single / double / template (backtick) quotes and triple-quoted
44
+ * strings. Mirrors the safer semantics of the group layer's `unquoteLiteral`:
45
+ * returns `null` for empty / nullish input so callers can uniformly skip
46
+ * captures whose value is missing, and returns the text unchanged when it
47
+ * carries no recognisable surrounding quotes (some grammars expose string
48
+ * content without quotes already).
49
+ */
50
+ export declare function unquoteSpringLiteral(raw: string): string | null;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared Spring route-annotation primitives.
3
+ *
4
+ * These are the low-level building blocks the two Spring route extractors —
5
+ * the ingestion-layer `route-extractors/spring.ts` (produces graph `Route`
6
+ * nodes) and the group-layer `group/extractors/http-patterns/java.ts`
7
+ * (produces cross-repo HTTP contracts) — would otherwise each maintain
8
+ * independently. Centralising the annotation→method map, the enclosing-class
9
+ * lookup, and the route-key filter keeps those semantics in one place so the
10
+ * two extractors can't drift apart.
11
+ *
12
+ * This module lives in `ingestion/` (the lower layer); the group layer imports
13
+ * from it, matching the existing `group → ingestion` dependency direction
14
+ * (e.g. `group/extractors/include-extractor.ts` already imports
15
+ * `ingestion/import-resolvers/utils.ts`). It MUST NOT import anything from
16
+ * `group/` to avoid a dependency cycle.
17
+ */
18
+ /**
19
+ * Spring shortcut method-annotation → HTTP verb.
20
+ *
21
+ * `@RequestMapping` is intentionally absent: on a method it carries no implicit
22
+ * verb (the verb lives in its `method = RequestMethod.X` attribute), and on a
23
+ * class it is a URL prefix rather than a route. Callers handle `@RequestMapping`
24
+ * separately.
25
+ */
26
+ export const METHOD_ANNOTATION_TO_HTTP = {
27
+ GetMapping: 'GET',
28
+ PostMapping: 'POST',
29
+ PutMapping: 'PUT',
30
+ DeleteMapping: 'DELETE',
31
+ PatchMapping: 'PATCH',
32
+ };
33
+ /**
34
+ * A named annotation argument contributes a route only when its member key is
35
+ * `path` or `value`; a positional argument (no key node) always qualifies.
36
+ * Drops Spring's non-route string attributes (`produces`, `consumes`,
37
+ * `headers`, `name`, `params`) that would otherwise be mis-read as routes.
38
+ */
39
+ export function isRouteMemberKey(keyNode) {
40
+ if (!keyNode)
41
+ return true;
42
+ return keyNode.text === 'path' || keyNode.text === 'value';
43
+ }
44
+ /**
45
+ * Find the nearest enclosing `class_declaration` ancestor for a node, or null
46
+ * if the node is top-level. Tree-sitter's `SyntaxNode.parent` walks one level
47
+ * at a time.
48
+ */
49
+ export function findEnclosingClass(node) {
50
+ let cur = node.parent;
51
+ while (cur) {
52
+ if (cur.type === 'class_declaration')
53
+ return cur;
54
+ cur = cur.parent;
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Strip enclosing quotes from a tree-sitter string-literal node's text.
60
+ * Handles single / double / template (backtick) quotes and triple-quoted
61
+ * strings. Mirrors the safer semantics of the group layer's `unquoteLiteral`:
62
+ * returns `null` for empty / nullish input so callers can uniformly skip
63
+ * captures whose value is missing, and returns the text unchanged when it
64
+ * carries no recognisable surrounding quotes (some grammars expose string
65
+ * content without quotes already).
66
+ */
67
+ export function unquoteSpringLiteral(raw) {
68
+ if (!raw)
69
+ return null;
70
+ if ((raw.startsWith('"""') && raw.endsWith('"""')) ||
71
+ (raw.startsWith("'''") && raw.endsWith("'''"))) {
72
+ return raw.slice(3, -3);
73
+ }
74
+ const first = raw[0];
75
+ const last = raw[raw.length - 1];
76
+ if ((first === '"' || first === "'" || first === '`') && last === first && raw.length >= 2) {
77
+ return raw.slice(1, -1);
78
+ }
79
+ return raw;
80
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Spring route annotation extractor for the ingestion pipeline.
3
+ *
4
+ * Extracts `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`,
5
+ * `@PatchMapping`, and `@RequestMapping` annotations from Java source files
6
+ * and returns `ExtractedDecoratorRoute[]` with class-level `@RequestMapping`
7
+ * prefixes already resolved per-class.
8
+ *
9
+ * This module is the ingestion-layer counterpart of
10
+ * `group/extractors/http-patterns/java.ts` (which extracts HTTP contracts
11
+ * for cross-repo matching). It uses the same tree-sitter capture approach:
12
+ * a single predicate-free query matches all route annotations generically,
13
+ * then a for-loop discriminates class-level prefixes from method-level routes
14
+ * by reading `@node.type` and the annotation name.
15
+ *
16
+ * The query is predicate-free to avoid the tree-sitter 0.21.x hazard where
17
+ * `#match?` / `#eq?` predicates in a top-level `[...]` alternation silently
18
+ * drop sibling-branch matches (see group-layer `JAVA_ROUTE_ANNOTATION_PATTERNS`
19
+ * header comment for details).
20
+ */
21
+ import Parser from 'tree-sitter';
22
+ import type { ExtractedDecoratorRoute } from '../workers/parse-worker.js';
23
+ /**
24
+ * Extract Spring route annotations from a parsed Java file.
25
+ *
26
+ * Uses a single tree-sitter query pass to capture all annotations, then
27
+ * discriminates class-level prefixes from method-level routes in a loop.
28
+ * Handles multiple classes per file, each with its own prefix.
29
+ *
30
+ * @param tree - tree-sitter parse tree
31
+ * @param filePath - relative file path (for `ExtractedDecoratorRoute.filePath`)
32
+ * @param lineOffset - line offset for pre-processing (usually 0)
33
+ * @returns Decorator routes with prefix already set per-class
34
+ */
35
+ export declare function extractSpringRoutes(tree: Parser.Tree, filePath: string, lineOffset?: number): ExtractedDecoratorRoute[];
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Spring route annotation extractor for the ingestion pipeline.
3
+ *
4
+ * Extracts `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`,
5
+ * `@PatchMapping`, and `@RequestMapping` annotations from Java source files
6
+ * and returns `ExtractedDecoratorRoute[]` with class-level `@RequestMapping`
7
+ * prefixes already resolved per-class.
8
+ *
9
+ * This module is the ingestion-layer counterpart of
10
+ * `group/extractors/http-patterns/java.ts` (which extracts HTTP contracts
11
+ * for cross-repo matching). It uses the same tree-sitter capture approach:
12
+ * a single predicate-free query matches all route annotations generically,
13
+ * then a for-loop discriminates class-level prefixes from method-level routes
14
+ * by reading `@node.type` and the annotation name.
15
+ *
16
+ * The query is predicate-free to avoid the tree-sitter 0.21.x hazard where
17
+ * `#match?` / `#eq?` predicates in a top-level `[...]` alternation silently
18
+ * drop sibling-branch matches (see group-layer `JAVA_ROUTE_ANNOTATION_PATTERNS`
19
+ * header comment for details).
20
+ */
21
+ import Parser from 'tree-sitter';
22
+ import Java from 'tree-sitter-java';
23
+ import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, unquoteSpringLiteral, } from './spring-shared.js';
24
+ /**
25
+ * Single predicate-free tree-sitter query that captures all route annotations
26
+ * on classes and methods. Discrimination by annotation name and node type
27
+ * happens in the loop below.
28
+ *
29
+ * Captures:
30
+ * @ann → annotation name identifier (RequestMapping, GetMapping, etc.)
31
+ * @node → enclosing declaration (class_declaration | method_declaration)
32
+ * @value → the string-literal argument
33
+ * @key → the named-argument member key (absent for positional form)
34
+ */
35
+ const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
36
+ [
37
+ (class_declaration
38
+ (modifiers
39
+ (annotation
40
+ name: (identifier) @ann
41
+ arguments: (annotation_argument_list (string_literal) @value)))) @node
42
+ (class_declaration
43
+ (modifiers
44
+ (annotation
45
+ name: (identifier) @ann
46
+ arguments: (annotation_argument_list
47
+ (element_value_pair
48
+ key: (identifier) @key
49
+ value: (string_literal) @value))))) @node
50
+ (method_declaration
51
+ (modifiers
52
+ (annotation
53
+ name: (identifier) @ann
54
+ arguments: (annotation_argument_list (string_literal) @value)))) @node
55
+ (method_declaration
56
+ (modifiers
57
+ (annotation
58
+ name: (identifier) @ann
59
+ arguments: (annotation_argument_list
60
+ (element_value_pair
61
+ key: (identifier) @key
62
+ value: (string_literal) @value))))) @node
63
+ ]
64
+ `);
65
+ /**
66
+ * Extract Spring route annotations from a parsed Java file.
67
+ *
68
+ * Uses a single tree-sitter query pass to capture all annotations, then
69
+ * discriminates class-level prefixes from method-level routes in a loop.
70
+ * Handles multiple classes per file, each with its own prefix.
71
+ *
72
+ * @param tree - tree-sitter parse tree
73
+ * @param filePath - relative file path (for `ExtractedDecoratorRoute.filePath`)
74
+ * @param lineOffset - line offset for pre-processing (usually 0)
75
+ * @returns Decorator routes with prefix already set per-class
76
+ */
77
+ export function extractSpringRoutes(tree, filePath, lineOffset = 0) {
78
+ const matches = ROUTE_ANNOTATION_QUERY.matches(tree.rootNode);
79
+ // Phase 1: collect class-level @RequestMapping prefixes keyed by node id
80
+ const prefixByClassId = new Map();
81
+ for (const match of matches) {
82
+ const caps = {};
83
+ for (const { name, node } of match.captures) {
84
+ caps[name] = node;
85
+ }
86
+ const annNode = caps['ann'];
87
+ const node = caps['node'];
88
+ const valueNode = caps['value'];
89
+ const keyNode = caps['key'];
90
+ if (!annNode || !node || !valueNode)
91
+ continue;
92
+ if (node.type === 'class_declaration' && annNode.text === 'RequestMapping') {
93
+ if (!isRouteMemberKey(keyNode))
94
+ continue;
95
+ const prefix = unquoteSpringLiteral(valueNode.text);
96
+ if (prefix !== null)
97
+ prefixByClassId.set(node.id, prefix);
98
+ }
99
+ }
100
+ // Phase 2: collect method-level routes and resolve their class prefix
101
+ const routes = [];
102
+ for (const match of matches) {
103
+ const caps = {};
104
+ for (const { name, node } of match.captures) {
105
+ caps[name] = node;
106
+ }
107
+ const annNode = caps['ann'];
108
+ const node = caps['node'];
109
+ const valueNode = caps['value'];
110
+ const keyNode = caps['key'];
111
+ if (!annNode || !node || !valueNode)
112
+ continue;
113
+ if (node.type !== 'method_declaration')
114
+ continue;
115
+ const ann = annNode.text;
116
+ const httpMethod = METHOD_ANNOTATION_TO_HTTP[ann];
117
+ if (!httpMethod)
118
+ continue; // skip @RequestMapping on methods (ambiguous verb)
119
+ if (!isRouteMemberKey(keyNode))
120
+ continue;
121
+ const routePath = unquoteSpringLiteral(valueNode.text);
122
+ if (routePath === null)
123
+ continue;
124
+ const enclosingClass = findEnclosingClass(node);
125
+ const classPrefix = enclosingClass ? (prefixByClassId.get(enclosingClass.id) ?? '') : '';
126
+ routes.push({
127
+ filePath,
128
+ routePath,
129
+ httpMethod,
130
+ decoratorName: ann,
131
+ lineNumber: annNode.startPosition.row + lineOffset,
132
+ ...(classPrefix ? { prefix: classPrefix } : {}),
133
+ });
134
+ }
135
+ return routes;
136
+ }
@@ -399,6 +399,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
399
399
  const returnType = match['@declaration.return-type']?.text;
400
400
  const templateConstraints = parseJsonCapture(match['@declaration.template-constraints']);
401
401
  const isExplicit = parseBooleanCapture(match['@declaration.is-explicit']);
402
+ const isDeleted = parseBooleanCapture(match['@declaration.is-deleted']);
402
403
  return {
403
404
  nodeId: makeDefId(filePath, anchor.range, type, nameCap.text),
404
405
  filePath,
@@ -413,6 +414,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
413
414
  ...(templateArguments !== undefined ? { templateArguments } : {}),
414
415
  ...(templateConstraints !== undefined ? { templateConstraints } : {}),
415
416
  ...(isExplicit === true ? { isExplicit: true } : {}),
417
+ ...(isDeleted === true ? { isDeleted: true } : {}),
416
418
  };
417
419
  }
418
420
  /** Parse an opaque JSON payload synthesized by per-language captures
@@ -922,6 +924,7 @@ const KNOWN_SUB_TAGS = new Set([
922
924
  '@declaration.return-type',
923
925
  '@declaration.template-constraints',
924
926
  '@declaration.is-explicit',
927
+ '@declaration.is-deleted',
925
928
  ]);
926
929
  /**
927
930
  * Return the anchor capture for a match — the one whose name begins with
@@ -289,6 +289,18 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
289
289
  }
290
290
  if (fnDef === undefined)
291
291
  continue;
292
+ if (fnDef.isDeleted === true) {
293
+ recordSuppressedOutcome(options.recordResolutionOutcome, {
294
+ phase: 'free-call-fallback',
295
+ filePath: parsed.filePath,
296
+ name: site.name,
297
+ range: site.atRange,
298
+ reason: 'selected-callable-deleted',
299
+ candidates: [fnDef],
300
+ });
301
+ handledSites.add(siteKey(parsed.filePath, site));
302
+ continue;
303
+ }
292
304
  if ((fnDefFromImplicitThis || fnDef.type === 'Method' || fnDef.type === 'Constructor') &&
293
305
  options.isCallableVisibleFromCaller !== undefined &&
294
306
  !options.isCallableVisibleFromCaller({
@@ -138,9 +138,12 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
138
138
  return 0;
139
139
  let n = 0;
140
140
  for (const implDef of impls) {
141
- const implMember = findOwnedMember(implDef.nodeId, memberName, model);
142
- if (implMember === undefined)
141
+ const implMember = pickOverload(implDef.nodeId, memberName, site, model, provider);
142
+ if (implMember === undefined ||
143
+ implMember === OVERLOAD_AMBIGUOUS ||
144
+ implMember.isDeleted === true) {
143
145
  continue;
146
+ }
144
147
  if (implMember.nodeId === primaryMemberDef.nodeId)
145
148
  continue;
146
149
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, implMember, 'interface-dispatch', seen, confidence, collapse);
@@ -179,12 +182,30 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
179
182
  ? extendsOnly(enclosingClass.nodeId)
180
183
  : scopes.methodDispatch.mroFor(enclosingClass.nodeId);
181
184
  let memberDef;
185
+ let ambiguousOwnerId;
182
186
  for (const ownerId of ancestors) {
183
- memberDef = findOwnedMember(ownerId, memberName, model);
184
- if (memberDef !== undefined)
187
+ const picked = site.kind === 'call'
188
+ ? pickOverload(ownerId, memberName, site, model, provider)
189
+ : findOwnedMember(ownerId, memberName, model);
190
+ if (picked === OVERLOAD_AMBIGUOUS) {
191
+ ambiguousOwnerId = ownerId;
192
+ break;
193
+ }
194
+ if (picked !== undefined) {
195
+ memberDef = picked;
185
196
  break;
197
+ }
198
+ }
199
+ if (ambiguousOwnerId !== undefined) {
200
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
201
+ handledSites.add(siteKey);
202
+ continue;
186
203
  }
187
204
  if (memberDef !== undefined) {
205
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
206
+ handledSites.add(siteKey);
207
+ continue;
208
+ }
188
209
  // Super/base calls resolve through the MRO chain, not
189
210
  // through imports — the ancestor method is found by
190
211
  // walking `methodDispatch.mroFor(enclosingClass)`, which
@@ -219,9 +240,9 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
219
240
  if (currentClass !== undefined) {
220
241
  const chain = [currentClass.nodeId, ...scopes.methodDispatch.mroFor(currentClass.nodeId)];
221
242
  let memberDef;
243
+ let ambiguousOwnerId;
222
244
  // Static-only filter (#1756 / U3): same shape as Case 4's
223
- // chain walk (skip-and-walk-on) but without overload
224
- // narrowing — Case 0 uses `findOwnedMember` directly. When
245
+ // overload-aware chain walk (skip-and-walk-on). When
225
246
  // an owner's resolved candidate is static-only (Kotlin
226
247
  // companion-promoted), continue to the next ancestor in
227
248
  // the MRO chain so a legitimate instance member can bind.
@@ -233,17 +254,29 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
233
254
  // shapes like `Logger.create("a")`), so there's no wrong
234
255
  // target to suppress.
235
256
  for (const ownerId of chain) {
236
- const candidate = findOwnedMember(ownerId, memberName, model);
237
- if (candidate === undefined)
238
- continue;
239
- if (provider.isStaticOnly?.(candidate) === true) {
240
- // Skip static-only candidate; walk to next ancestor.
257
+ const picked = site.kind === 'call'
258
+ ? pickFirstNonStaticOnly(ownerId, memberName, site, model, provider)
259
+ : findOwnedMember(ownerId, memberName, model);
260
+ if (picked === OVERLOAD_AMBIGUOUS) {
261
+ ambiguousOwnerId = ownerId;
262
+ break;
263
+ }
264
+ if (picked === STATIC_ONLY_FILTERED || picked === undefined) {
241
265
  continue;
242
266
  }
243
- memberDef = candidate;
267
+ memberDef = picked;
244
268
  break;
245
269
  }
270
+ if (ambiguousOwnerId !== undefined) {
271
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
272
+ handledSites.add(siteKey);
273
+ continue;
274
+ }
246
275
  if (memberDef !== undefined) {
276
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
277
+ handledSites.add(siteKey);
278
+ continue;
279
+ }
247
280
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
248
281
  if (ok)
249
282
  emitted++;
@@ -290,6 +323,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
290
323
  }
291
324
  if (languageResolution?.kind === 'resolved') {
292
325
  const memberDef = languageResolution.definition;
326
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
327
+ handledSites.add(siteKey);
328
+ continue;
329
+ }
293
330
  const reason = site.kind === 'write' || site.kind === 'read'
294
331
  ? site.kind
295
332
  : memberDef.filePath !== parsed.filePath
@@ -357,6 +394,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
357
394
  continue;
358
395
  }
359
396
  if (memberDef !== undefined) {
397
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
398
+ handledSites.add(siteKey);
399
+ continue;
400
+ }
360
401
  const reason = site.kind === 'write' || site.kind === 'read'
361
402
  ? site.kind
362
403
  : memberDef.filePath !== parsed.filePath
@@ -373,11 +414,16 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
373
414
  }
374
415
  // ── Case 1: namespace receiver ───────────────────────────────
375
416
  const targetFiles = namespaceTargets.get(receiverName);
376
- if (targetFiles !== undefined) {
417
+ if (targetFiles !== undefined && provider.resolveQualifiedReceiverMember === undefined) {
377
418
  let found = false;
378
419
  for (const targetFile of targetFiles) {
379
420
  const memberDef = findExportedDef(targetFile, memberName, index);
380
421
  if (memberDef !== undefined) {
422
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
423
+ handledSites.add(siteKey);
424
+ found = true;
425
+ break;
426
+ }
381
427
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
382
428
  if (ok)
383
429
  emitted++;
@@ -413,6 +459,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
413
459
  continue;
414
460
  }
415
461
  if (memberDef !== undefined) {
462
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
463
+ handledSites.add(siteKey);
464
+ continue;
465
+ }
416
466
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
417
467
  if (ok)
418
468
  emitted++;
@@ -425,9 +475,17 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
425
475
  if (classDef !== undefined) {
426
476
  const chain = [classDef.nodeId, ...scopes.methodDispatch.mroFor(classDef.nodeId)];
427
477
  let memberDef;
478
+ let ambiguousOwnerId;
428
479
  for (const ownerId of chain) {
429
- memberDef = findOwnedMember(ownerId, memberName, model);
430
- if (memberDef !== undefined) {
480
+ const picked = site.kind === 'call'
481
+ ? pickOverload(ownerId, memberName, site, model, provider)
482
+ : findOwnedMember(ownerId, memberName, model);
483
+ if (picked === OVERLOAD_AMBIGUOUS) {
484
+ ambiguousOwnerId = ownerId;
485
+ break;
486
+ }
487
+ if (picked !== undefined) {
488
+ memberDef = picked;
431
489
  // The MRO chain is most-derived-first ([classDef, ...ancestors]).
432
490
  // If the most-derived definition is arity-incompatible with the
433
491
  // call site, PHP throws ArgumentCountError at runtime — it does
@@ -441,7 +499,16 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
441
499
  break;
442
500
  }
443
501
  }
502
+ if (ambiguousOwnerId !== undefined) {
503
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
504
+ handledSites.add(siteKey);
505
+ continue;
506
+ }
444
507
  if (memberDef !== undefined) {
508
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
509
+ handledSites.add(siteKey);
510
+ continue;
511
+ }
445
512
  const reason = site.kind === 'write' || site.kind === 'read'
446
513
  ? site.kind
447
514
  : memberDef.filePath !== parsed.filePath
@@ -466,8 +533,22 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
466
533
  for (const targetFile3 of targetFiles3) {
467
534
  const classDef3 = findExportedDef(targetFile3, className, index);
468
535
  if (classDef3 !== undefined) {
469
- const memberDef = findOwnedMember(classDef3.nodeId, memberName, model);
470
- if (memberDef !== undefined) {
536
+ const picked = site.kind === 'call'
537
+ ? pickOverload(classDef3.nodeId, memberName, site, model, provider)
538
+ : findOwnedMember(classDef3.nodeId, memberName, model);
539
+ if (picked === OVERLOAD_AMBIGUOUS) {
540
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, classDef3.nodeId, memberName, model, provider);
541
+ handledSites.add(siteKey);
542
+ found3 = true;
543
+ break;
544
+ }
545
+ if (picked !== undefined) {
546
+ const memberDef = picked;
547
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
548
+ handledSites.add(siteKey);
549
+ found3 = true;
550
+ break;
551
+ }
471
552
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen);
472
553
  if (ok) {
473
554
  emitted++;
@@ -504,8 +585,9 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
504
585
  if (ownerDef !== undefined) {
505
586
  const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
506
587
  let memberDef;
507
- // Static-only filter (#1756 / U3): mirrors Case 0's chain
508
- // walk `findOwnedMember` without overload narrowing. When
588
+ let ambiguousOwnerId;
589
+ // Static-only filter (#1756 / U3): mirrors Case 0's
590
+ // overload-aware chain walk. When
509
591
  // a static-only candidate is found at an ancestor, walk on
510
592
  // so a legitimate instance member can bind. If the entire
511
593
  // chain is static-only, no edge is emitted (Case 3b is fed
@@ -513,16 +595,29 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
513
595
  // `emitReferencesViaLookup` for compound shapes, so no
514
596
  // handled-site marker is needed for chain-only-static).
515
597
  for (const ownerId of chain) {
516
- const candidate = findOwnedMember(ownerId, memberName, model);
517
- if (candidate === undefined)
518
- continue;
519
- if (provider.isStaticOnly?.(candidate) === true) {
598
+ const picked = site.kind === 'call'
599
+ ? pickFirstNonStaticOnly(ownerId, memberName, site, model, provider)
600
+ : findOwnedMember(ownerId, memberName, model);
601
+ if (picked === OVERLOAD_AMBIGUOUS) {
602
+ ambiguousOwnerId = ownerId;
603
+ break;
604
+ }
605
+ if (picked === STATIC_ONLY_FILTERED || picked === undefined) {
520
606
  continue;
521
607
  }
522
- memberDef = candidate;
608
+ memberDef = picked;
523
609
  break;
524
610
  }
611
+ if (ambiguousOwnerId !== undefined) {
612
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
613
+ handledSites.add(siteKey);
614
+ continue;
615
+ }
525
616
  if (memberDef !== undefined) {
617
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
618
+ handledSites.add(siteKey);
619
+ continue;
620
+ }
526
621
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
527
622
  if (ok)
528
623
  emitted++;
@@ -574,6 +669,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
574
669
  }
575
670
  if (languageResolution?.kind === 'resolved') {
576
671
  const memberDef = languageResolution.definition;
672
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
673
+ handledSites.add(siteKey);
674
+ continue;
675
+ }
577
676
  const reason = site.kind === 'write' || site.kind === 'read'
578
677
  ? site.kind
579
678
  : memberDef.filePath !== parsed.filePath
@@ -657,6 +756,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
657
756
  continue;
658
757
  }
659
758
  if (memberDef !== undefined) {
759
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
760
+ handledSites.add(siteKey);
761
+ continue;
762
+ }
660
763
  // For read/write ACCESSES, mirror the legacy DAG's reason
661
764
  // convention so consumers asserting `reason === 'write'`
662
765
  // keep working.
@@ -713,6 +816,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
713
816
  continue;
714
817
  }
715
818
  if (picked !== undefined) {
819
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, picked)) {
820
+ handledSites.add(siteKey);
821
+ continue;
822
+ }
716
823
  // Static-only filter (#1756 / U3): unlike Case 4 there's no
717
824
  // MRO chain to walk here — Case 5 dispatches on a single
718
825
  // owner via `pickOverload`. When the picked candidate is
@@ -873,6 +980,20 @@ function pickFirstNonStaticOnly(ownerId, memberName, site, model, provider) {
873
980
  return OVERLOAD_AMBIGUOUS;
874
981
  return candidates[0] ?? overloads[0];
875
982
  }
983
+ function suppressDeletedCallTarget(record, filePath, site, target) {
984
+ if (site.kind !== 'call' || target.isDeleted !== true)
985
+ return false;
986
+ record?.({
987
+ kind: 'suppressed',
988
+ phase: 'receiver-bound-calls',
989
+ filePath,
990
+ name: site.name,
991
+ range: site.atRange,
992
+ reason: 'selected-callable-deleted',
993
+ candidateIds: [target.nodeId],
994
+ });
995
+ return true;
996
+ }
876
997
  function recordReceiverOverloadSuppression(record, filePath, site, ownerId, memberName, model, provider) {
877
998
  if (record === undefined)
878
999
  return;