gitnexus 1.6.6-rc.74 → 1.6.6-rc.76

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.
@@ -4,12 +4,17 @@ export type { HttpDetection, HttpLanguagePlugin, HttpRole } from './types.js';
4
4
  * Glob for files worth scanning for HTTP routes. Kept alongside the
5
5
  * registry so adding a new language widens the glob in one edit.
6
6
  *
7
+ * `.kt`/`.kts` are always present in the glob even when the optional
8
+ * `tree-sitter-kotlin` grammar isn't installed — `getPluginForFile`
9
+ * will return `undefined` for those files in that case, so the
10
+ * orchestrator simply skips them at scan time without erroring.
11
+ *
7
12
  * `.vue` / `.svelte` files are intentionally omitted for the source-scan
8
13
  * path — they need their own grammar-aware extraction and the existing
9
14
  * regex fallback for them was never very accurate. The graph-assisted
10
15
  * Strategy A still handles them via the ingestion pipeline.
11
16
  */
12
- export declare const HTTP_SCAN_GLOB = "**/*.{ts,tsx,js,jsx,java,go,py,php}";
17
+ export declare const HTTP_SCAN_GLOB = "**/*.{ts,tsx,js,jsx,java,kt,kts,go,py,php}";
13
18
  /**
14
19
  * Return the HTTP plugin registered for the given file's extension,
15
20
  * or `undefined` if the extension is not registered.
@@ -1,6 +1,7 @@
1
1
  import * as path from 'node:path';
2
2
  import { isBladeTemplateFilename } from '../../../../_shared/index.js';
3
3
  import { JAVA_HTTP_PLUGIN } from './java.js';
4
+ import { KOTLIN_HTTP_PLUGIN } from './kotlin.js';
4
5
  import { GO_HTTP_PLUGIN } from './go.js';
5
6
  import { PYTHON_HTTP_PLUGIN } from './python.js';
6
7
  import { PHP_HTTP_PLUGIN } from './php.js';
@@ -14,6 +15,11 @@ import { JAVASCRIPT_HTTP_PLUGIN, TYPESCRIPT_HTTP_PLUGIN, TSX_HTTP_PLUGIN } from
14
15
  * new language, drop a `http-patterns/<lang>.ts` that exports a
15
16
  * `HttpLanguagePlugin`, import it here and register the extension(s).
16
17
  * No edits to `http-route-extractor.ts` are required.
18
+ *
19
+ * Optional grammar plugins (e.g. `kotlin.ts`, which depends on the
20
+ * optionalDependency `tree-sitter-kotlin`) export `null` when the
21
+ * native binding is unavailable; we skip registration in that case so
22
+ * a missing optional grammar never crashes the orchestrator.
17
23
  */
18
24
  const REGISTRY = {
19
25
  '.java': JAVA_HTTP_PLUGIN,
@@ -25,16 +31,25 @@ const REGISTRY = {
25
31
  '.ts': TYPESCRIPT_HTTP_PLUGIN,
26
32
  '.tsx': TSX_HTTP_PLUGIN,
27
33
  };
34
+ if (KOTLIN_HTTP_PLUGIN) {
35
+ REGISTRY['.kt'] = KOTLIN_HTTP_PLUGIN;
36
+ REGISTRY['.kts'] = KOTLIN_HTTP_PLUGIN;
37
+ }
28
38
  /**
29
39
  * Glob for files worth scanning for HTTP routes. Kept alongside the
30
40
  * registry so adding a new language widens the glob in one edit.
31
41
  *
42
+ * `.kt`/`.kts` are always present in the glob even when the optional
43
+ * `tree-sitter-kotlin` grammar isn't installed — `getPluginForFile`
44
+ * will return `undefined` for those files in that case, so the
45
+ * orchestrator simply skips them at scan time without erroring.
46
+ *
32
47
  * `.vue` / `.svelte` files are intentionally omitted for the source-scan
33
48
  * path — they need their own grammar-aware extraction and the existing
34
49
  * regex fallback for them was never very accurate. The graph-assisted
35
50
  * Strategy A still handles them via the ingestion pipeline.
36
51
  */
37
- export const HTTP_SCAN_GLOB = '**/*.{ts,tsx,js,jsx,java,go,py,php}';
52
+ export const HTTP_SCAN_GLOB = '**/*.{ts,tsx,js,jsx,java,kt,kts,go,py,php}';
38
53
  /**
39
54
  * Return the HTTP plugin registered for the given file's extension,
40
55
  * or `undefined` if the extension is not registered.
@@ -20,6 +20,19 @@ const METHOD_ANNOTATION_TO_HTTP = {
20
20
  PatchMapping: 'PATCH',
21
21
  };
22
22
  // ─── Provider: Spring class-level @RequestMapping prefix ──────────────
23
+ // Two patterns are needed because the AST shape differs depending on
24
+ // whether the annotation uses a positional argument or a named one:
25
+ // @RequestMapping("/api") → (annotation_argument_list (string_literal))
26
+ // @RequestMapping(path = "/api") → (annotation_argument_list (element_value_pair key:(identifier) value:(string_literal)))
27
+ // @RequestMapping(value = "/api") → same as above
28
+ //
29
+ // The named-argument pattern MUST constrain the `key` field to the route
30
+ // member names (`path`/`value`); without it, the query also captures
31
+ // non-route attributes such as `produces`, `consumes`, `headers`, `name`,
32
+ // `params` (their right-hand string literals would be mis-extracted as
33
+ // route prefixes — e.g. `produces = "application/json"` would corrupt
34
+ // every method route under that controller). The sibling
35
+ // `topic-patterns/java.ts` uses the same `key:` constraint approach.
23
36
  const SPRING_CLASS_PREFIX_PATTERNS = compilePatterns({
24
37
  name: 'java-spring-class-prefix',
25
38
  language: Java,
@@ -34,9 +47,26 @@ const SPRING_CLASS_PREFIX_PATTERNS = compilePatterns({
34
47
  arguments: (annotation_argument_list (string_literal) @prefix)))) @class
35
48
  `,
36
49
  },
50
+ {
51
+ meta: {},
52
+ query: `
53
+ (class_declaration
54
+ (modifiers
55
+ (annotation
56
+ name: (identifier) @ann (#eq? @ann "RequestMapping")
57
+ arguments: (annotation_argument_list
58
+ (element_value_pair
59
+ key: (identifier) @key (#match? @key "^(path|value)$")
60
+ value: (string_literal) @prefix))))) @class
61
+ `,
62
+ },
37
63
  ],
38
64
  });
39
65
  // ─── Provider: Spring @(Get|Post|...)Mapping method annotations ───────
66
+ // Same dual-pattern approach: positional vs named argument. The named
67
+ // pattern restricts the annotation member name to `path`/`value` to
68
+ // avoid capturing unrelated string-valued attributes
69
+ // (`produces`, `consumes`, `headers`, `name`, `params`, ...).
40
70
  const SPRING_METHOD_ROUTE_PATTERNS = compilePatterns({
41
71
  name: 'java-spring-method-route',
42
72
  language: Java,
@@ -52,6 +82,20 @@ const SPRING_METHOD_ROUTE_PATTERNS = compilePatterns({
52
82
  name: (identifier) @method_name) @method
53
83
  `,
54
84
  },
85
+ {
86
+ meta: {},
87
+ query: `
88
+ (method_declaration
89
+ (modifiers
90
+ (annotation
91
+ name: (identifier) @ann (#match? @ann "^(Get|Post|Put|Delete|Patch)Mapping$")
92
+ arguments: (annotation_argument_list
93
+ (element_value_pair
94
+ key: (identifier) @key (#match? @key "^(path|value)$")
95
+ value: (string_literal) @path))))
96
+ name: (identifier) @method_name) @method
97
+ `,
98
+ },
55
99
  ],
56
100
  });
57
101
  // ─── Consumer: Spring RestTemplate (object-named + method-named) ──────
@@ -0,0 +1,8 @@
1
+ import type { HttpLanguagePlugin } from './types.js';
2
+ /**
3
+ * The exported plugin is `null` when tree-sitter-kotlin's native
4
+ * binding is unavailable. `http-patterns/index.ts` checks for null
5
+ * before registering `.kt`/`.kts` so missing optional grammars never
6
+ * crash the orchestrator.
7
+ */
8
+ export declare const KOTLIN_HTTP_PLUGIN: HttpLanguagePlugin | null;
@@ -0,0 +1,457 @@
1
+ import { createRequire } from 'node:module';
2
+ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
3
+ /**
4
+ * Kotlin HTTP plugin (Spring providers + consumers).
5
+ *
6
+ * **Providers** (#1849) — Spring `@RequestMapping` class prefixes and
7
+ * `@(Get|Post|...)Mapping` method annotations on Kotlin Spring Boot
8
+ * controllers. Both positional shorthand (`@GetMapping("/x")`) and
9
+ * named annotation arguments (`@GetMapping(value = "/x")` and
10
+ * `@GetMapping(path = "/x")`) are supported.
11
+ *
12
+ * **Consumers** (this PR) — three call-site patterns common in Kotlin
13
+ * Spring projects:
14
+ *
15
+ * 1. `restTemplate.getForObject("/x", ...)` and friends
16
+ * 2. `webClient.get().uri("/x")` (short form, 1 verb hop + 1 uri hop)
17
+ * 3. `Request.Builder().url("/x")` (OkHttp)
18
+ *
19
+ * The long-form `webClient.method(HttpMethod.X).uri("/y")` chain is
20
+ * intentionally deferred to a follow-up: it requires walk-up logic
21
+ * to recover the verb from a sibling `call_expression`, and we can
22
+ * land 80% of real-world Kotlin Spring consumer coverage with the
23
+ * three simpler patterns above.
24
+ *
25
+ * tree-sitter-kotlin (fwcd) AST shapes used here:
26
+ * class_declaration
27
+ * modifiers
28
+ * annotation
29
+ * constructor_invocation
30
+ * user_type → type_identifier ← annotation name
31
+ * value_arguments
32
+ * value_argument
33
+ * (simple_identifier "=")? ← absent for positional, present for named
34
+ * string_literal
35
+ * type_identifier ← class name
36
+ *
37
+ * Consumer call shape (Kotlin chains everything via `navigation_expression`):
38
+ * call_expression ← outer `.uri("/x")` or `.url("/x")`
39
+ * navigation_expression
40
+ * call_expression ← inner `.get()` / `Request.Builder()` / `restTemplate.x`
41
+ * navigation_expression
42
+ * simple_identifier ← receiver: `webClient` / `Request` / `restTemplate`
43
+ * navigation_suffix ← `.method` / `.Builder` / `.getForObject`
44
+ * call_suffix (value_arguments)
45
+ * navigation_suffix ← `.uri` / `.url`
46
+ * call_suffix
47
+ * value_arguments
48
+ * value_argument
49
+ * string_literal ← the path
50
+ *
51
+ * tree-sitter-kotlin is an optional npm dependency — when its native
52
+ * binding is unavailable the plugin gracefully exports `null` and
53
+ * `http-patterns/index.ts` skips registration for `.kt`/`.kts` files.
54
+ */
55
+ const _require = createRequire(import.meta.url);
56
+ /** Loaded lazily; null when the grammar binding isn't installed. */
57
+ let Kotlin = null;
58
+ try {
59
+ Kotlin = _require('tree-sitter-kotlin');
60
+ }
61
+ catch {
62
+ Kotlin = null;
63
+ }
64
+ const METHOD_ANNOTATION_TO_HTTP = {
65
+ GetMapping: 'GET',
66
+ PostMapping: 'POST',
67
+ PutMapping: 'PUT',
68
+ DeleteMapping: 'DELETE',
69
+ PatchMapping: 'PATCH',
70
+ };
71
+ /**
72
+ * RestTemplate method-name → HTTP verb. Mirrors the Java plugin's
73
+ * `REST_TEMPLATE_TO_HTTP` (java.ts) so a polyglot repo emits the
74
+ * same contract IDs from .java and .kt sources.
75
+ */
76
+ const REST_TEMPLATE_TO_HTTP = {
77
+ getForObject: 'GET',
78
+ getForEntity: 'GET',
79
+ postForObject: 'POST',
80
+ postForEntity: 'POST',
81
+ put: 'PUT',
82
+ delete: 'DELETE',
83
+ patchForObject: 'PATCH',
84
+ };
85
+ /**
86
+ * WebClient short-form verb → HTTP verb. The reactive WebClient API
87
+ * exposes `.get()`, `.post()`, `.put()`, `.delete()`, `.patch()` as
88
+ * one-liners that return a `RequestHeadersUriSpec` whose `.uri(...)`
89
+ * carries the path. We capture both pieces in a single query (see
90
+ * `WEB_CLIENT_SHORT_PATTERNS` below) and translate the verb here.
91
+ */
92
+ const WEB_CLIENT_SHORT_TO_HTTP = {
93
+ get: 'GET',
94
+ post: 'POST',
95
+ put: 'PUT',
96
+ delete: 'DELETE',
97
+ patch: 'PATCH',
98
+ };
99
+ /**
100
+ * Build the plugin only if the Kotlin grammar is available. Compiling
101
+ * the queries against a null grammar would throw at module load time
102
+ * and abort the whole http-route-extractor module.
103
+ */
104
+ function buildKotlinPlugin(language) {
105
+ // ─── Provider: Spring class-level @RequestMapping prefix ──────────────
106
+ // Two patterns mirror the Java plugin's positional vs named split:
107
+ // @RequestMapping("/api") → value_argument has string_literal as its first named child
108
+ // @RequestMapping(path = "/api") → value_argument has [simple_identifier @key, string_literal]
109
+ // @RequestMapping(value = "/api") → same as above, with key="value"
110
+ //
111
+ // Tree-sitter-kotlin grammar (fwcd 0.3.8) does NOT have a separate
112
+ // node for named arguments — both positional and named forms share
113
+ // `value_argument`. The positional pattern uses the immediate-child
114
+ // anchor `.` so it only matches when the string_literal is the FIRST
115
+ // named child (i.e. no preceding simple_identifier "=" prefix). The
116
+ // named pattern explicitly captures the simple_identifier and uses
117
+ // `#match?` to restrict it to `path`/`value`, matching the same
118
+ // safety bar that the Java plugin enforces (see java.ts and the
119
+ // sibling topic-patterns/java.ts for the analogous constraint).
120
+ //
121
+ // Without the `key:` constraint the named query would also capture
122
+ // unrelated attributes like `produces`, `consumes`, `headers`,
123
+ // `name`, `params` — emitting bogus route contracts (a regression
124
+ // identical to the one Claude flagged on PR #1834 for Java).
125
+ const SPRING_CLASS_PREFIX_PATTERNS = compilePatterns({
126
+ name: 'kotlin-spring-class-prefix',
127
+ language,
128
+ patterns: [
129
+ {
130
+ meta: {},
131
+ query: `
132
+ (class_declaration
133
+ (modifiers
134
+ (annotation
135
+ (constructor_invocation
136
+ (user_type (type_identifier) @ann (#eq? @ann "RequestMapping"))
137
+ (value_arguments
138
+ (value_argument . (string_literal) @prefix)))))
139
+ (type_identifier) @cls) @class
140
+ `,
141
+ },
142
+ {
143
+ meta: {},
144
+ query: `
145
+ (class_declaration
146
+ (modifiers
147
+ (annotation
148
+ (constructor_invocation
149
+ (user_type (type_identifier) @ann (#eq? @ann "RequestMapping"))
150
+ (value_arguments
151
+ (value_argument
152
+ (simple_identifier) @key (#match? @key "^(path|value)$")
153
+ (string_literal) @prefix)))))
154
+ (type_identifier) @cls) @class
155
+ `,
156
+ },
157
+ ],
158
+ });
159
+ // ─── Provider: Spring @(Get|Post|...)Mapping method annotations ───────
160
+ // Same dual-pattern positional/named approach. The Kotlin AST puts the
161
+ // function name (`simple_identifier`) outside the `modifiers` subtree,
162
+ // so we capture it from `function_declaration` directly.
163
+ const SPRING_METHOD_ROUTE_PATTERNS = compilePatterns({
164
+ name: 'kotlin-spring-method-route',
165
+ language,
166
+ patterns: [
167
+ {
168
+ meta: {},
169
+ query: `
170
+ (function_declaration
171
+ (modifiers
172
+ (annotation
173
+ (constructor_invocation
174
+ (user_type (type_identifier) @ann (#match? @ann "^(Get|Post|Put|Delete|Patch)Mapping$"))
175
+ (value_arguments
176
+ (value_argument . (string_literal) @path)))))
177
+ (simple_identifier) @method_name) @method
178
+ `,
179
+ },
180
+ {
181
+ meta: {},
182
+ query: `
183
+ (function_declaration
184
+ (modifiers
185
+ (annotation
186
+ (constructor_invocation
187
+ (user_type (type_identifier) @ann (#match? @ann "^(Get|Post|Put|Delete|Patch)Mapping$"))
188
+ (value_arguments
189
+ (value_argument
190
+ (simple_identifier) @key (#match? @key "^(path|value)$")
191
+ (string_literal) @path)))))
192
+ (simple_identifier) @method_name) @method
193
+ `,
194
+ },
195
+ ],
196
+ });
197
+ // ─── Consumer: Spring RestTemplate ────────────────────────────────────
198
+ // Kotlin call-site shape mirrors the Java plugin's
199
+ // `REST_TEMPLATE_PATTERNS`, but goes through tree-sitter-kotlin's
200
+ // `navigation_expression` instead of Java's `method_invocation`:
201
+ //
202
+ // restTemplate.getForObject("/x", User::class.java)
203
+ //
204
+ // becomes
205
+ //
206
+ // call_expression
207
+ // navigation_expression
208
+ // simple_identifier "restTemplate"
209
+ // navigation_suffix → simple_identifier "getForObject"
210
+ // call_suffix
211
+ // value_arguments
212
+ // value_argument . string_literal "/x" ← captured
213
+ // value_argument User::class.java
214
+ //
215
+ // The receiver name is constrained to `restTemplate` (#eq? @obj),
216
+ // matching the Java plugin's heuristic. This means a non-conventional
217
+ // field name (e.g. `userServiceTemplate`) will not be picked up;
218
+ // that's the same trade-off already accepted on the Java side.
219
+ const REST_TEMPLATE_PATTERNS = compilePatterns({
220
+ name: 'kotlin-rest-template',
221
+ language,
222
+ patterns: [
223
+ {
224
+ meta: {},
225
+ query: `
226
+ (call_expression
227
+ (navigation_expression
228
+ (simple_identifier) @obj (#eq? @obj "restTemplate")
229
+ (navigation_suffix (simple_identifier) @method))
230
+ (call_suffix
231
+ (value_arguments . (value_argument . (string_literal) @path))))
232
+ `,
233
+ },
234
+ ],
235
+ });
236
+ // ─── Consumer: Spring WebClient (short form) ──────────────────────────
237
+ // Reactive WebClient exposes one-liner verb helpers:
238
+ //
239
+ // webClient.get().uri("/x").retrieve().awaitBody<T>()
240
+ // webClient.post().uri("/x")...
241
+ //
242
+ // The chain `webClient.get().uri("/x")` parses as two nested
243
+ // `call_expression` nodes — the OUTER call is `.uri("/x")` and the
244
+ // INNER call is `webClient.get()`. We anchor on the outer call and
245
+ // require:
246
+ // - inner receiver is `webClient`
247
+ // - inner suffix is one of the HTTP verbs (#match?)
248
+ // - outer suffix is exactly `uri`
249
+ // - outer call's first value_argument is a string literal
250
+ //
251
+ // The long-form `webClient.method(HttpMethod.GET).uri("/x")` chain
252
+ // uses an extra navigation hop and an enum field access — it's
253
+ // intentionally out of scope here (see file header).
254
+ const WEB_CLIENT_SHORT_PATTERNS = compilePatterns({
255
+ name: 'kotlin-web-client-short',
256
+ language,
257
+ patterns: [
258
+ {
259
+ meta: {},
260
+ query: `
261
+ (call_expression
262
+ (navigation_expression
263
+ (call_expression
264
+ (navigation_expression
265
+ (simple_identifier) @obj (#eq? @obj "webClient")
266
+ (navigation_suffix
267
+ (simple_identifier) @verb (#match? @verb "^(get|post|put|delete|patch)$")))
268
+ (call_suffix (value_arguments)))
269
+ (navigation_suffix (simple_identifier) @uri (#eq? @uri "uri")))
270
+ (call_suffix
271
+ (value_arguments . (value_argument . (string_literal) @path))))
272
+ `,
273
+ },
274
+ ],
275
+ });
276
+ // ─── Consumer: OkHttp Request.Builder().url("/x") ─────────────────────
277
+ // Kotlin parses `Request.Builder()` as a `call_expression` whose
278
+ // callee is a `navigation_expression` (Request → .Builder), NOT as
279
+ // Java's `object_creation_expression`. The chain `.url("/x")` then
280
+ // wraps that in another `call_expression`. The query mirrors Java's
281
+ // `OK_HTTP_PATTERNS` (java.ts) but adapts the node types.
282
+ //
283
+ // Receiver `Request` is constrained by name (#eq? @cls); a project
284
+ // that imports OkHttp's `Request` under an alias (`import okhttp3.Request as OkRequest`)
285
+ // would not be picked up — this matches the Java plugin's heuristic.
286
+ //
287
+ // **Known limitation — verb defaults to GET.** OkHttp encodes the
288
+ // verb on a *sibling* call further down the builder chain (e.g.
289
+ // `.post(body)` / `.get()` / `.delete()`), not on `.url(...)` itself.
290
+ // This query intentionally does not walk the chain to recover the
291
+ // verb — it emits `method: 'GET'` for every match, mirroring
292
+ // `java.ts:OK_HTTP_PATTERNS`. So a `Request.Builder().url("/x").post(body).build()`
293
+ // call becomes `http::GET::/x`, not `http::POST::/x`. This is the
294
+ // same trade-off Java has accepted; pinned by an anti-overreach
295
+ // test in `http-route-extractor.test.ts` so a future verb-walk
296
+ // implementation has to update this comment in lockstep.
297
+ const OK_HTTP_PATTERNS = compilePatterns({
298
+ name: 'kotlin-okhttp',
299
+ language,
300
+ patterns: [
301
+ {
302
+ meta: {},
303
+ query: `
304
+ (call_expression
305
+ (navigation_expression
306
+ (call_expression
307
+ (navigation_expression
308
+ (simple_identifier) @cls (#eq? @cls "Request")
309
+ (navigation_suffix (simple_identifier) @builder (#eq? @builder "Builder")))
310
+ (call_suffix (value_arguments)))
311
+ (navigation_suffix (simple_identifier) @method (#eq? @method "url")))
312
+ (call_suffix
313
+ (value_arguments . (value_argument . (string_literal) @path))))
314
+ `,
315
+ },
316
+ ],
317
+ });
318
+ /**
319
+ * Find the nearest enclosing class_declaration ancestor for a node, or
320
+ * null if the node is top-level. Mirrors the Java plugin's helper.
321
+ */
322
+ function findEnclosingClass(node) {
323
+ let cur = node.parent;
324
+ while (cur) {
325
+ if (cur.type === 'class_declaration')
326
+ return cur;
327
+ cur = cur.parent;
328
+ }
329
+ return null;
330
+ }
331
+ /**
332
+ * Join a class-level prefix and a method-level path. Identical
333
+ * semantics to the Java plugin: strip leading/trailing slashes on
334
+ * the prefix, strip leading slashes on the method path, ensure a
335
+ * single slash between them.
336
+ */
337
+ function joinPath(prefix, methodPath) {
338
+ const cleanPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
339
+ const cleanSub = methodPath.replace(/^\/+/, '');
340
+ if (!cleanPrefix)
341
+ return `/${cleanSub}`;
342
+ return `/${cleanPrefix}/${cleanSub}`;
343
+ }
344
+ return {
345
+ name: 'kotlin-http',
346
+ language,
347
+ scan(tree) {
348
+ const out = [];
349
+ // ─── Class prefixes ─────────────────────────────────────────────
350
+ const prefixByClassId = new Map();
351
+ for (const match of runCompiledPatterns(SPRING_CLASS_PREFIX_PATTERNS, tree)) {
352
+ const prefixNode = match.captures.prefix;
353
+ const classNode = match.captures.class;
354
+ if (!prefixNode || !classNode)
355
+ continue;
356
+ const prefix = unquoteLiteral(prefixNode.text);
357
+ if (prefix !== null)
358
+ prefixByClassId.set(classNode.id, prefix);
359
+ }
360
+ // ─── Method routes ──────────────────────────────────────────────
361
+ for (const match of runCompiledPatterns(SPRING_METHOD_ROUTE_PATTERNS, tree)) {
362
+ const annNode = match.captures.ann;
363
+ const pathNode = match.captures.path;
364
+ const nameNode = match.captures.method_name;
365
+ const methodNode = match.captures.method;
366
+ if (!annNode || !pathNode || !methodNode)
367
+ continue;
368
+ const httpMethod = METHOD_ANNOTATION_TO_HTTP[annNode.text];
369
+ if (!httpMethod)
370
+ continue;
371
+ const rawPath = unquoteLiteral(pathNode.text);
372
+ if (rawPath === null)
373
+ continue;
374
+ const enclosingClass = findEnclosingClass(methodNode);
375
+ const prefix = enclosingClass ? (prefixByClassId.get(enclosingClass.id) ?? '') : '';
376
+ const fullPath = joinPath(prefix, rawPath);
377
+ out.push({
378
+ role: 'provider',
379
+ framework: 'spring',
380
+ method: httpMethod,
381
+ path: fullPath,
382
+ name: nameNode?.text ?? null,
383
+ confidence: 0.8,
384
+ });
385
+ }
386
+ // ─── Consumers: RestTemplate ────────────────────────────────────
387
+ for (const match of runCompiledPatterns(REST_TEMPLATE_PATTERNS, tree)) {
388
+ const methodNode = match.captures.method;
389
+ const pathNode = match.captures.path;
390
+ if (!methodNode || !pathNode)
391
+ continue;
392
+ const httpMethod = REST_TEMPLATE_TO_HTTP[methodNode.text];
393
+ if (!httpMethod)
394
+ continue;
395
+ const path = unquoteLiteral(pathNode.text);
396
+ if (path === null)
397
+ continue;
398
+ out.push({
399
+ role: 'consumer',
400
+ framework: 'spring-rest-template',
401
+ method: httpMethod,
402
+ path,
403
+ name: null,
404
+ confidence: 0.7,
405
+ });
406
+ }
407
+ // ─── Consumers: WebClient short form (.get()/.post()/etc → .uri) ─
408
+ for (const match of runCompiledPatterns(WEB_CLIENT_SHORT_PATTERNS, tree)) {
409
+ const verbNode = match.captures.verb;
410
+ const pathNode = match.captures.path;
411
+ if (!verbNode || !pathNode)
412
+ continue;
413
+ const httpMethod = WEB_CLIENT_SHORT_TO_HTTP[verbNode.text];
414
+ if (!httpMethod)
415
+ continue;
416
+ const path = unquoteLiteral(pathNode.text);
417
+ if (path === null)
418
+ continue;
419
+ out.push({
420
+ role: 'consumer',
421
+ framework: 'spring-web-client',
422
+ method: httpMethod,
423
+ path,
424
+ name: null,
425
+ confidence: 0.7,
426
+ });
427
+ }
428
+ // ─── Consumers: OkHttp Request.Builder().url("path") ────────────
429
+ for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
430
+ const pathNode = match.captures.path;
431
+ if (!pathNode)
432
+ continue;
433
+ const path = unquoteLiteral(pathNode.text);
434
+ if (path === null)
435
+ continue;
436
+ out.push({
437
+ role: 'consumer',
438
+ framework: 'okhttp',
439
+ method: 'GET',
440
+ path,
441
+ name: null,
442
+ confidence: 0.7,
443
+ });
444
+ }
445
+ return out;
446
+ },
447
+ };
448
+ }
449
+ /**
450
+ * The exported plugin is `null` when tree-sitter-kotlin's native
451
+ * binding is unavailable. `http-patterns/index.ts` checks for null
452
+ * before registering `.kt`/`.kts` so missing optional grammars never
453
+ * crash the orchestrator.
454
+ */
455
+ export const KOTLIN_HTTP_PLUGIN = Kotlin
456
+ ? buildKotlinPlugin(Kotlin)
457
+ : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.74",
3
+ "version": "1.6.6-rc.76",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",