gitnexus 1.6.9-rc.4 → 1.6.9-rc.5

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.
@@ -0,0 +1,39 @@
1
+ import Parser from 'tree-sitter';
2
+ /**
3
+ * Resolve a statically-derivable path argument to a literal path: a bare
4
+ * string literal, a `URI.create("/x")` call, or a `UriComponentsBuilder`
5
+ * fluent chain. Genuinely dynamic arguments → null.
6
+ */
7
+ export declare function extractStaticPathExpression(node: Parser.SyntaxNode): string | null;
8
+ /**
9
+ * Infer an OkHttp request verb by scanning the builder chain around the matched
10
+ * `.url(...)` call: a `.get()/.head()/.post()/.put()/.delete()/.patch()` helper
11
+ * (before or after `.url()`), or a `.method("VERB", …)` literal, wins. Returns
12
+ * `'GET'` only when the chain has NO verb call at all (a bare `.url(...).build()`
13
+ * — OkHttp's real default). Returns `null` for an explicit `.method(verb, …)`
14
+ * whose verb is a non-literal: the verb is set but not statically resolvable, so
15
+ * the caller skips the call rather than asserting a wrong GET (parity with the
16
+ * WebClient long-form variable-bound-verb behavior, which also skips).
17
+ */
18
+ export declare function inferOkHttpMethod(urlCall: Parser.SyntaxNode): string | null;
19
+ /**
20
+ * Infer a Java-`HttpClient` request verb by walking UP the builder chain from the
21
+ * matched `.uri(URI.create("..."))` call: the first `.GET()/.POST()/.PUT()/`
22
+ * `.DELETE()/.HEAD()` verb-helper, or a `.method("VERB", body)` literal, wins.
23
+ * Returns `'GET'` only when the chain has no verb call (a bare `.build()` — the
24
+ * builder's real default). Returns `null` for an explicit `.method(verb, …)` with
25
+ * a non-literal verb (skip, not a guessed GET). The scan is transparent to
26
+ * neutral calls anywhere in the chain (`.header()`/`.timeout()`/`.version()`),
27
+ * before or after `.uri()`, so neither a header/timeout hop nor a verb call's
28
+ * position drops the contract.
29
+ */
30
+ export declare function inferHttpClientMethod(uriCall: Parser.SyntaxNode): string | null;
31
+ /** True when `urlCall`'s receiver chain bottoms out on a `new Request.Builder()`
32
+ * object-creation (descending the `.object` chain past any intervening calls). */
33
+ export declare function okHttpUrlRootsAtBuilder(urlCall: Parser.SyntaxNode): boolean;
34
+ /** True when `uriCall`'s receiver chain includes a `HttpRequest.newBuilder()`. */
35
+ export declare function httpClientUriRootsAtNewBuilder(uriCall: Parser.SyntaxNode): boolean;
36
+ /** True when a `HttpRequest.newBuilder(URI.create(...))` chain ALSO calls `.uri(...)`
37
+ * later — a later `.uri()` overrides the constructor URI at runtime, so the
38
+ * constructor-arg path must NOT be emitted (the `.uri()` query emits the override). */
39
+ export declare function httpClientChainHasUriCall(newBuilderCall: Parser.SyntaxNode): boolean;
@@ -0,0 +1,248 @@
1
+ import { unquoteLiteral } from '../tree-sitter-scanner.js';
2
+ // ─── Statically-resolvable consumer path + builder verb-walk helpers ──────
3
+ // RestTemplate calls increasingly pass a non-literal path argument that is
4
+ // still statically derivable — `URI.create("/x")` or a `UriComponentsBuilder`
5
+ // fluent chain. These helpers resolve those shapes to a literal path; a
6
+ // genuinely dynamic argument (a variable, a non-`URI`/`UriComponentsBuilder`
7
+ // call) resolves to null and the call site is skipped. This module also owns
8
+ // the OkHttp / Java-HttpClient builder verb-walks (`inferOkHttpMethod` /
9
+ // `inferHttpClientMethod`), which recover the request verb by walking UP the
10
+ // fluent chain from the matched `.url(...)` / `.uri(...)` call. Extracted from
11
+ // java.ts (#2268) so that plugin stays under ~1000 lines; the `methodInvocation*`
12
+ // primitives + `firstLiteralArgument` are module-internal (shared by the path
13
+ // resolvers and the verb-walks), while the path resolver and the two verb-walks
14
+ // are java.ts's only entry points here.
15
+ function methodInvocationName(node) {
16
+ return node.type === 'method_invocation' ? (node.childForFieldName('name')?.text ?? null) : null;
17
+ }
18
+ function methodInvocationObject(node) {
19
+ return node.type === 'method_invocation' ? node.childForFieldName('object') : null;
20
+ }
21
+ function methodInvocationArguments(node) {
22
+ const argsNode = node.type === 'method_invocation' ? node.childForFieldName('arguments') : null;
23
+ return argsNode?.namedChildren ?? [];
24
+ }
25
+ function firstLiteralArgument(node) {
26
+ const first = methodInvocationArguments(node)[0];
27
+ return first?.type === 'string_literal' ? unquoteLiteral(first.text) : null;
28
+ }
29
+ /** Resolve a `URI.create("/path")` call to its literal path; null otherwise. */
30
+ function extractUriCreatePath(node) {
31
+ if (node.type !== 'method_invocation')
32
+ return null;
33
+ if (methodInvocationObject(node)?.text !== 'URI' || methodInvocationName(node) !== 'create')
34
+ return null;
35
+ return firstLiteralArgument(node);
36
+ }
37
+ // Join a builder base with a sub-path using exactly one separating slash. This
38
+ // is intentionally NOT the shared `joinPath`: `joinPath` force-prepends `/`,
39
+ // whereas `appendPath` must preserve an absolute/host base (`fromHttpUrl`
40
+ // "https://host/api") so the host survives until `normalizeConsumerPath` strips
41
+ // it downstream. Do not unify the two.
42
+ function appendPath(base, subPath) {
43
+ if (!base)
44
+ return subPath.startsWith('/') ? subPath : `/${subPath}`;
45
+ if (!subPath)
46
+ return base;
47
+ return `${base.replace(/\/+$/, '')}/${subPath.replace(/^\/+/, '')}`;
48
+ }
49
+ /**
50
+ * Resolve a `UriComponentsBuilder` fluent chain to its literal path. Seed
51
+ * methods (`fromPath`/`fromUriString`/`fromHttpUrl`) return the literal arg
52
+ * VERBATIM — a `fromHttpUrl("https://host/api")` seed keeps its host, which the
53
+ * shared `normalizeConsumerPath` later reduces to the path (the same single
54
+ * normalization point every other consumer path goes through). `path` and
55
+ * `pathSegment` append literal segments; `build`/`toUriString`/`toUri`/`encode`
56
+ * and the `query*` family pass through (query attributes do not change the
57
+ * path). Any non-literal segment or unknown call → null.
58
+ */
59
+ // A UriComponentsBuilder chain deeper than this is not realistic source; cap the
60
+ // recursion so a pathological / machine-generated chain returns null instead of
61
+ // overflowing the stack (mirrors the project's other AST-depth guards).
62
+ const MAX_BUILDER_DEPTH = 100;
63
+ function extractUriComponentsBuilderPath(node, depth = 0) {
64
+ if (depth > MAX_BUILDER_DEPTH)
65
+ return null;
66
+ if (node.type !== 'method_invocation')
67
+ return null;
68
+ const name = methodInvocationName(node);
69
+ const objectNode = methodInvocationObject(node);
70
+ if ((name === 'fromPath' || name === 'fromUriString' || name === 'fromHttpUrl') &&
71
+ objectNode?.text === 'UriComponentsBuilder') {
72
+ // Strip any `?query` baked into the seed literal so a later `.path()` appends
73
+ // to a clean base; otherwise the sub-path is glued after the query
74
+ // (`/base?x=1/sub`) and normalizeHttpPath truncates the whole tail at `?`.
75
+ // A host prefix (`https://h/api`) is preserved and stripped downstream by
76
+ // normalizeConsumerPath.
77
+ const seed = firstLiteralArgument(node);
78
+ return seed === null ? null : seed.split('?')[0];
79
+ }
80
+ if (!objectNode)
81
+ return null;
82
+ if (name === 'path') {
83
+ const base = extractUriComponentsBuilderPath(objectNode, depth + 1);
84
+ const subPath = firstLiteralArgument(node);
85
+ if (base === null || subPath === null)
86
+ return null;
87
+ // Spring `UriComponentsBuilder.path(p)` appends `p` VERBATIM (no slash
88
+ // inserted — unlike `pathSegment`, which slash-joins), then normalizes the
89
+ // full path to collapse duplicate slashes. So `fromPath("/api").path("users")`
90
+ // → `/apiusers`, while `.path("/users")` → `/api/users`, and a trailing-slash
91
+ // base collapses (`/api/` + `/users` → `/api/users`). The `(?<!:)` keeps a
92
+ // scheme `://` in a host seed intact; the downstream normalizer is the other
93
+ // slash-collapser for consumer paths (see KTD2 — do not remove it).
94
+ return (base + subPath).replace(/(?<!:)\/{2,}/g, '/');
95
+ }
96
+ if (name === 'pathSegment') {
97
+ const base = extractUriComponentsBuilderPath(objectNode, depth + 1);
98
+ if (base === null)
99
+ return null;
100
+ const args = methodInvocationArguments(node);
101
+ const segments = args
102
+ .map((arg) => (arg.type === 'string_literal' ? unquoteLiteral(arg.text) : null))
103
+ .filter((segment) => segment !== null);
104
+ if (segments.length !== args.length)
105
+ return null; // a non-literal segment defeats static resolution
106
+ return segments.reduce((acc, segment) => appendPath(acc, segment), base);
107
+ }
108
+ if (name === 'build' ||
109
+ name === 'toUriString' ||
110
+ name === 'toUri' ||
111
+ name === 'encode' ||
112
+ name === 'query' ||
113
+ name === 'queryParam' ||
114
+ name === 'queryParams' ||
115
+ name === 'replaceQuery' ||
116
+ name === 'replaceQueryParam' ||
117
+ name === 'replaceQueryParams')
118
+ return extractUriComponentsBuilderPath(objectNode, depth + 1);
119
+ return null;
120
+ }
121
+ /**
122
+ * Resolve a statically-derivable path argument to a literal path: a bare
123
+ * string literal, a `URI.create("/x")` call, or a `UriComponentsBuilder`
124
+ * fluent chain. Genuinely dynamic arguments → null.
125
+ */
126
+ export function extractStaticPathExpression(node) {
127
+ if (node.type === 'string_literal')
128
+ return unquoteLiteral(node.text);
129
+ return extractUriCreatePath(node) ?? extractUriComponentsBuilderPath(node);
130
+ }
131
+ // ─── Builder verb-walks ───────────────────────────────────────────────
132
+ // OkHttp / Java-HttpClient encode the request verb on a SIBLING call elsewhere in
133
+ // the fluent chain, not on the matched path call. Both queries capture the path
134
+ // call (`.url(...)` / `.uri(...)`); these helpers recover the verb by scanning
135
+ // the WHOLE chain — transparent to neutral calls (`.addHeader()`/`.header()`/
136
+ // `.timeout()`/`.version()`) wherever they sit relative to the path call, so the
137
+ // verb resolves whether it precedes or follows the path call.
138
+ /** Every `method_invocation` in the fluent chain `pathCall` belongs to, ordered
139
+ * innermost (next to the construction) → outermost (terminal). Lets the verb-walk
140
+ * find the verb wherever it sits, and lets the root gates inspect the chain base. */
141
+ function builderChainCalls(pathCall) {
142
+ let innermost = pathCall;
143
+ let obj = methodInvocationObject(innermost);
144
+ while (obj?.type === 'method_invocation') {
145
+ innermost = obj;
146
+ obj = methodInvocationObject(innermost);
147
+ }
148
+ const calls = [innermost];
149
+ let cur = innermost;
150
+ let parent = cur.parent;
151
+ while (parent?.type === 'method_invocation' && methodInvocationObject(parent)?.id === cur.id) {
152
+ calls.push(parent);
153
+ cur = parent;
154
+ parent = parent.parent;
155
+ }
156
+ return calls;
157
+ }
158
+ /** Scan the chain for the verb a `name`-matching helper or a `.method("LITERAL")`
159
+ * call sets, resolving the LAST such call — each verb-setter overwrites the
160
+ * previous at runtime, so on the (non-idiomatic) chain that sets two verbs the
161
+ * one nearest the terminal wins. `null` means "verb is set but not statically
162
+ * resolvable" (a non-literal/empty `.method(verb)`) → the caller skips rather
163
+ * than guessing. `defaultVerb` is returned only when the chain has no verb call
164
+ * at all (a bare build). `verbHelpers` are matched on the method NAME (OkHttp
165
+ * helpers are lowercase, HttpClient helpers uppercase). */
166
+ function inferBuilderVerb(pathCall, verbHelpers, defaultVerb) {
167
+ let lastVerbCall = null;
168
+ for (const call of builderChainCalls(pathCall)) {
169
+ if (call.id === pathCall.id)
170
+ continue; // the path call itself is never the verb
171
+ const name = methodInvocationName(call);
172
+ if (name === 'method' || (name !== null && verbHelpers.includes(name)))
173
+ lastVerbCall = call;
174
+ }
175
+ if (lastVerbCall === null)
176
+ return defaultVerb; // no verb call → builder default
177
+ const name = methodInvocationName(lastVerbCall);
178
+ // Explicit `.method(...)`: a non-empty string-literal verb resolves; a
179
+ // non-literal (variable-bound) OR empty-string verb is unresolvable → null
180
+ // (skip), NOT a guessed default or a malformed empty-method contract.
181
+ if (name === 'method') {
182
+ const verb = firstLiteralArgument(lastVerbCall);
183
+ return verb ? verb.toUpperCase() : null;
184
+ }
185
+ return name === null ? defaultVerb : name.toUpperCase(); // a verb-helper name
186
+ }
187
+ const OK_HTTP_VERB_HELPERS = ['get', 'head', 'post', 'put', 'delete', 'patch'];
188
+ const HTTP_CLIENT_VERB_HELPERS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD'];
189
+ /**
190
+ * Infer an OkHttp request verb by scanning the builder chain around the matched
191
+ * `.url(...)` call: a `.get()/.head()/.post()/.put()/.delete()/.patch()` helper
192
+ * (before or after `.url()`), or a `.method("VERB", …)` literal, wins. Returns
193
+ * `'GET'` only when the chain has NO verb call at all (a bare `.url(...).build()`
194
+ * — OkHttp's real default). Returns `null` for an explicit `.method(verb, …)`
195
+ * whose verb is a non-literal: the verb is set but not statically resolvable, so
196
+ * the caller skips the call rather than asserting a wrong GET (parity with the
197
+ * WebClient long-form variable-bound-verb behavior, which also skips).
198
+ */
199
+ export function inferOkHttpMethod(urlCall) {
200
+ return inferBuilderVerb(urlCall, OK_HTTP_VERB_HELPERS, 'GET');
201
+ }
202
+ /**
203
+ * Infer a Java-`HttpClient` request verb by walking UP the builder chain from the
204
+ * matched `.uri(URI.create("..."))` call: the first `.GET()/.POST()/.PUT()/`
205
+ * `.DELETE()/.HEAD()` verb-helper, or a `.method("VERB", body)` literal, wins.
206
+ * Returns `'GET'` only when the chain has no verb call (a bare `.build()` — the
207
+ * builder's real default). Returns `null` for an explicit `.method(verb, …)` with
208
+ * a non-literal verb (skip, not a guessed GET). The scan is transparent to
209
+ * neutral calls anywhere in the chain (`.header()`/`.timeout()`/`.version()`),
210
+ * before or after `.uri()`, so neither a header/timeout hop nor a verb call's
211
+ * position drops the contract.
212
+ */
213
+ export function inferHttpClientMethod(uriCall) {
214
+ return inferBuilderVerb(uriCall, HTTP_CLIENT_VERB_HELPERS, 'GET');
215
+ }
216
+ // ─── Builder-chain root gates (anti-overreach) ────────────────────────
217
+ // The path queries match a bare `.url(...)` / `.uri(URI.create(...))` call on ANY
218
+ // receiver so that a builder call BEFORE the path call (`new Request.Builder()`
219
+ // `.addHeader(...).url(...)`, `HttpRequest.newBuilder().version(v).uri(...)`) is
220
+ // still captured. These gates re-impose the framework anchor in JS — only a chain
221
+ // that bottoms out on the right construction emits, so a `.url(...)`/`.uri(...)`
222
+ // on an unrelated object does not.
223
+ /** True when `urlCall`'s receiver chain bottoms out on a `new Request.Builder()`
224
+ * object-creation (descending the `.object` chain past any intervening calls). */
225
+ export function okHttpUrlRootsAtBuilder(urlCall) {
226
+ let obj = methodInvocationObject(urlCall);
227
+ while (obj?.type === 'method_invocation')
228
+ obj = methodInvocationObject(obj);
229
+ return (obj?.type === 'object_creation_expression' &&
230
+ obj.childForFieldName('type')?.text === 'Request.Builder');
231
+ }
232
+ /** True when `uriCall`'s receiver chain includes a `HttpRequest.newBuilder()`. */
233
+ export function httpClientUriRootsAtNewBuilder(uriCall) {
234
+ let obj = methodInvocationObject(uriCall);
235
+ while (obj?.type === 'method_invocation') {
236
+ if (methodInvocationName(obj) === 'newBuilder' &&
237
+ methodInvocationObject(obj)?.text === 'HttpRequest')
238
+ return true;
239
+ obj = methodInvocationObject(obj);
240
+ }
241
+ return false;
242
+ }
243
+ /** True when a `HttpRequest.newBuilder(URI.create(...))` chain ALSO calls `.uri(...)`
244
+ * later — a later `.uri()` overrides the constructor URI at runtime, so the
245
+ * constructor-arg path must NOT be emitted (the `.uri()` query emits the override). */
246
+ export function httpClientChainHasUriCall(newBuilderCall) {
247
+ return builderChainCalls(newBuilderCall).some((call) => call.id !== newBuilderCall.id && methodInvocationName(call) === 'uri');
248
+ }
@@ -2,6 +2,7 @@ import Java from 'tree-sitter-java';
2
2
  import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
3
3
  import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, } from '../../../ingestion/route-extractors/spring-shared.js';
4
4
  import { REST_TEMPLATE_TO_HTTP, WEB_CLIENT_SHORT_TO_HTTP, WEB_CLIENT_LONG_VERB_RE, EXCHANGE_ANNOTATION_TO_HTTP, parseRequestLine, pushPrefix, joinPath, scanSpringInheritanceProject, OPENFEIGN_FRAMEWORK, HTTP_INTERFACE_FRAMEWORK, FEIGN_CONFIDENCE, REQUEST_LINE_CONFIDENCE, EXCHANGE_CONFIDENCE, } from './spring-consumer-shared.js';
5
+ import { extractStaticPathExpression, inferOkHttpMethod, inferHttpClientMethod, okHttpUrlRootsAtBuilder, httpClientUriRootsAtNewBuilder, httpClientChainHasUriCall, } from './java-static-path.js';
5
6
  /**
6
7
  * Java HTTP plugin. Handles:
7
8
  * - Spring `@RequestMapping` class prefixes + `@(Get|Post|...)Mapping` method annotations
@@ -64,17 +65,15 @@ import { REST_TEMPLATE_TO_HTTP, WEB_CLIENT_SHORT_TO_HTTP, WEB_CLIENT_LONG_VERB_R
64
65
  // sidesteps that hazard entirely; all name/key discrimination lives in the
65
66
  // for-loop, where it reads as straight-line code.
66
67
  //
67
- // KNOWN LIMITATION — fully-qualified route annotations are not matched. `@ann`
68
- // binds `name: (identifier)`, but a FQN annotation (`@org.springframework…
69
- // GetMapping("/x")`) parses its name as a `scoped_identifier`, which this query
70
- // does not match, so its route is not extracted. (The class is still recognized
71
- // as a controller`hasAnnotation` trailing-segment-matches the FQN only the
72
- // route-string extraction is missed.) In practice annotations are imported and
73
- // written by simple name, so this is rare. It is a minor asymmetry with the
74
- // Kotlin plugin, whose grammar models a FQN as separate `type_identifier`
75
- // segments that its route queries DO match. Aligning Java would mean matching
76
- // `scoped_identifier` too; that is deferred to avoid re-keying existing Java
77
- // contracts via the predicate hazard above. Pinned by an anti-overreach test.
68
+ // FULLY-QUALIFIED route annotations ARE matched. `@ann` binds either an
69
+ // `identifier` (simple name) or a `scoped_identifier` (a FQN annotation such as
70
+ // `@org.springframework…GetMapping("/x")`); the for-loop normalizes the name to
71
+ // its trailing segment via `simpleName` before discriminating. This is a node-
72
+ // type widening onlythe query stays predicate-free, so it does NOT reintroduce
73
+ // the bucket hazard above, and a simple name maps to itself so existing Java
74
+ // contracts are unchanged (only previously-unmatched FQN annotations gain
75
+ // routes). This brings Java to parity with the Kotlin plugin, whose grammar
76
+ // already matches FQN route annotations.
78
77
  const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
79
78
  name: 'java-route-annotation',
80
79
  language: Java,
@@ -86,17 +85,17 @@ const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
86
85
  (class_declaration
87
86
  (modifiers
88
87
  (annotation
89
- name: (identifier) @ann
88
+ name: [(identifier) (scoped_identifier)] @ann
90
89
  arguments: (annotation_argument_list [(string_literal) @value (element_value_array_initializer (string_literal) @value)])))) @node
91
90
  (interface_declaration
92
91
  (modifiers
93
92
  (annotation
94
- name: (identifier) @ann
93
+ name: [(identifier) (scoped_identifier)] @ann
95
94
  arguments: (annotation_argument_list [(string_literal) @value (element_value_array_initializer (string_literal) @value)])))) @node
96
95
  (class_declaration
97
96
  (modifiers
98
97
  (annotation
99
- name: (identifier) @ann
98
+ name: [(identifier) (scoped_identifier)] @ann
100
99
  arguments: (annotation_argument_list
101
100
  (element_value_pair
102
101
  key: (identifier) @key
@@ -104,7 +103,7 @@ const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
104
103
  (interface_declaration
105
104
  (modifiers
106
105
  (annotation
107
- name: (identifier) @ann
106
+ name: [(identifier) (scoped_identifier)] @ann
108
107
  arguments: (annotation_argument_list
109
108
  (element_value_pair
110
109
  key: (identifier) @key
@@ -112,13 +111,13 @@ const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
112
111
  (method_declaration
113
112
  (modifiers
114
113
  (annotation
115
- name: (identifier) @ann
114
+ name: [(identifier) (scoped_identifier)] @ann
116
115
  arguments: (annotation_argument_list [(string_literal) @value (element_value_array_initializer (string_literal) @value)])))
117
116
  name: (identifier) @member) @node
118
117
  (method_declaration
119
118
  (modifiers
120
119
  (annotation
121
- name: (identifier) @ann
120
+ name: [(identifier) (scoped_identifier)] @ann
122
121
  arguments: (annotation_argument_list
123
122
  (element_value_pair
124
123
  key: (identifier) @key
@@ -154,7 +153,7 @@ const REST_TEMPLATE_PATTERNS = compilePatterns({
154
153
  (method_invocation
155
154
  object: (identifier) @obj (#eq? @obj "restTemplate")
156
155
  name: (identifier) @method
157
- arguments: (argument_list . (string_literal) @path))
156
+ arguments: (argument_list . (_) @path))
158
157
  `,
159
158
  },
160
159
  ],
@@ -170,7 +169,7 @@ const REST_TEMPLATE_EXCHANGE_PATTERNS = compilePatterns({
170
169
  object: (identifier) @obj (#eq? @obj "restTemplate")
171
170
  name: (identifier) @method (#eq? @method "exchange")
172
171
  arguments: (argument_list
173
- . (string_literal) @path
172
+ . (_) @path
174
173
  (field_access
175
174
  object: (identifier) @httpMethodCls (#eq? @httpMethodCls "HttpMethod")
176
175
  field: (identifier) @http_method)))
@@ -226,10 +225,14 @@ const WEB_CLIENT_LONG_FORM_PATTERNS = compilePatterns({
226
225
  },
227
226
  ],
228
227
  });
229
- // ─── Consumer: OkHttp `new Request.Builder().url("path")` ─────────────
230
- // Note: `Request.Builder` is a `scoped_type_identifier` whose text includes
231
- // the dot, so `#eq?` against the literal string matches cleanly (no need
232
- // to escape a regex dot).
228
+ // ─── Consumer: OkHttp `Request.Builder()url("path")` ─────────────────
229
+ // Match a bare `.url("literal")` call on ANY receiver, capturing it as `@call`.
230
+ // `okHttpUrlRootsAtBuilder` (JS) then verifies the receiver chain bottoms out on
231
+ // `new Request.Builder()` re-imposing the framework anchor while allowing
232
+ // builder calls BEFORE `.url()` (`new Request.Builder().addHeader(...).url("/x")`)
233
+ // that the old object-direct query dropped, and rejecting a `.url(...)` on an
234
+ // unrelated object. The verb is recovered by `inferOkHttpMethod` scanning the
235
+ // whole chain (java-static-path.ts).
233
236
  const OK_HTTP_PATTERNS = compilePatterns({
234
237
  name: 'java-okhttp',
235
238
  language: Java,
@@ -238,14 +241,21 @@ const OK_HTTP_PATTERNS = compilePatterns({
238
241
  meta: {},
239
242
  query: `
240
243
  (method_invocation
241
- object: (object_creation_expression
242
- type: (scoped_type_identifier) @type (#eq? @type "Request.Builder"))
243
244
  name: (identifier) @method (#eq? @method "url")
244
- arguments: (argument_list . (string_literal) @path))
245
+ arguments: (argument_list . (string_literal) @path)) @call
245
246
  `,
246
247
  },
247
248
  ],
248
249
  });
250
+ // Match a bare `.uri(URI.create("literal"))` call on ANY receiver, capturing it
251
+ // as `@call`. `httpClientUriRootsAtNewBuilder` (JS) verifies the chain includes
252
+ // `HttpRequest.newBuilder()` — allowing calls BEFORE `.uri()` (`.version(v)`
253
+ // `.uri(...)`) that the old object-direct query dropped, and rejecting a `.uri(...)`
254
+ // on an unrelated object (e.g. WebClient). The verb (a `.GET()/.POST()/.PUT()/`
255
+ // `.DELETE()/.HEAD()` helper, a `.method("VERB", body)` literal, or the bare-build
256
+ // default) is recovered by `inferHttpClientMethod` scanning the whole chain.
257
+ // Matching `.uri(...)` regardless of a trailing `.build()` mirrors the accepted
258
+ // OkHttp over-match posture.
249
259
  const JAVA_HTTP_CLIENT_PATTERNS = compilePatterns({
250
260
  name: 'java-http-client',
251
261
  language: Java,
@@ -254,18 +264,35 @@ const JAVA_HTTP_CLIENT_PATTERNS = compilePatterns({
254
264
  meta: {},
255
265
  query: `
256
266
  (method_invocation
257
- object: (method_invocation
258
- object: (method_invocation
259
- object: (identifier) @builderCls (#eq? @builderCls "HttpRequest")
260
- name: (identifier) @newBuilder (#eq? @newBuilder "newBuilder")
261
- arguments: (argument_list))
262
- name: (identifier) @uri_method (#eq? @uri_method "uri")
263
- arguments: (argument_list
264
- (method_invocation
265
- object: (identifier) @uriCls (#eq? @uriCls "URI")
266
- name: (identifier) @create (#eq? @create "create")
267
- arguments: (argument_list . (string_literal) @path))))
268
- name: (identifier) @http_method (#match? @http_method "^(GET|POST|PUT|DELETE)$"))
267
+ name: (identifier) @uri_method (#eq? @uri_method "uri")
268
+ arguments: (argument_list
269
+ (method_invocation
270
+ object: (identifier) @uriCls (#eq? @uriCls "URI")
271
+ name: (identifier) @create (#eq? @create "create")
272
+ arguments: (argument_list . (string_literal) @path)))) @call
273
+ `,
274
+ },
275
+ ],
276
+ });
277
+ // Constructor-arg form: `HttpRequest.newBuilder(URI.create("..."))` — the path is
278
+ // the `newBuilder(...)` argument, with no `.uri()` call. Captures the `newBuilder`
279
+ // call as `@call`; the emission skips it when a later `.uri(...)` overrides the
280
+ // constructor URI (`httpClientChainHasUriCall`), so the `.uri()` query owns that case.
281
+ const JAVA_HTTP_CLIENT_CTOR_PATTERNS = compilePatterns({
282
+ name: 'java-http-client-ctor',
283
+ language: Java,
284
+ patterns: [
285
+ {
286
+ meta: {},
287
+ query: `
288
+ (method_invocation
289
+ object: (identifier) @builderCls (#eq? @builderCls "HttpRequest")
290
+ name: (identifier) @newBuilder (#eq? @newBuilder "newBuilder")
291
+ arguments: (argument_list
292
+ (method_invocation
293
+ object: (identifier) @uriCls (#eq? @uriCls "URI")
294
+ name: (identifier) @create (#eq? @create "create")
295
+ arguments: (argument_list . (string_literal) @path)))) @call
269
296
  `,
270
297
  },
271
298
  ],
@@ -307,6 +334,17 @@ function findEnclosingInterface(node) {
307
334
  function getNodeName(node) {
308
335
  return node.childForFieldName('name')?.text ?? null;
309
336
  }
337
+ /**
338
+ * Trailing segment of a possibly fully-qualified annotation name
339
+ * (`org.springframework.web.bind.annotation.GetMapping` → `GetMapping`). The
340
+ * route query binds `@ann` to either an `identifier` (simple) or a
341
+ * `scoped_identifier` (FQN); normalizing here lets the one for-loop discriminate
342
+ * on the simple name in both cases. A simple name maps to itself, so this never
343
+ * changes how a non-FQN annotation is classified.
344
+ */
345
+ function simpleName(text) {
346
+ return text.split('.').pop() ?? text;
347
+ }
310
348
  function hasAnnotation(node, names) {
311
349
  const modifiers = node.namedChildren.find((child) => child.type === 'modifiers');
312
350
  if (!modifiers)
@@ -316,9 +354,8 @@ function hasAnnotation(node, names) {
316
354
  while (stack.length > 0) {
317
355
  const cur = stack.pop();
318
356
  const annotationName = cur.childForFieldName('name')?.text ?? '';
319
- const simpleName = annotationName.split('.').pop() ?? annotationName;
320
357
  if ((cur.type === 'annotation' || cur.type === 'marker_annotation') &&
321
- (allowed.has(annotationName) || allowed.has(simpleName))) {
358
+ (allowed.has(annotationName) || allowed.has(simpleName(annotationName)))) {
322
359
  return true;
323
360
  }
324
361
  stack.push(...cur.namedChildren);
@@ -359,7 +396,13 @@ function scanRouteAnnotations(tree) {
359
396
  const valueNode = captures.value;
360
397
  if (!annNode || !node || !valueNode)
361
398
  continue;
362
- const ann = annNode.text;
399
+ // Discrimination is on the trailing segment only (`simpleName`), so a
400
+ // non-Spring annotation whose last segment collides with a route annotation
401
+ // (e.g. `@com.evil.GetMapping("/x")`) is treated as a route. This is the
402
+ // same accepted trailing-segment trade-off `hasAnnotation` already makes and
403
+ // the intended parity with the Kotlin plugin — package-origin gating would
404
+ // break that parity and is deliberately not done.
405
+ const ann = simpleName(annNode.text);
363
406
  const keyNode = captures.key; // undefined for the positional shape
364
407
  if (node.type === 'method_declaration') {
365
408
  // Method-level: a Spring `@(Get|...)Mapping` route, or native `@RequestLine`.
@@ -630,7 +673,7 @@ export const JAVA_HTTP_PLUGIN = {
630
673
  const httpMethod = REST_TEMPLATE_TO_HTTP[methodNode.text];
631
674
  if (!httpMethod)
632
675
  continue;
633
- const path = unquoteLiteral(pathNode.text);
676
+ const path = extractStaticPathExpression(pathNode);
634
677
  if (path === null)
635
678
  continue;
636
679
  out.push({
@@ -647,7 +690,7 @@ export const JAVA_HTTP_PLUGIN = {
647
690
  const pathNode = match.captures.path;
648
691
  if (!httpMethodNode || !pathNode)
649
692
  continue;
650
- const path = unquoteLiteral(pathNode.text);
693
+ const path = extractStaticPathExpression(pathNode);
651
694
  if (path === null)
652
695
  continue;
653
696
  out.push({
@@ -709,37 +752,84 @@ export const JAVA_HTTP_PLUGIN = {
709
752
  });
710
753
  }
711
754
  // ─── Consumers: OkHttp Request.Builder().url("path") ────────────
755
+ // Match any `.url("literal")`, gate to chains rooting at `new Request.Builder()`
756
+ // (so a call before `.url()` is captured but an unrelated `.url()` is not), then
757
+ // recover the verb (`.post()`/`.method("X")`) by scanning the builder chain.
712
758
  for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
759
+ const callNode = match.captures.call;
713
760
  const pathNode = match.captures.path;
714
- if (!pathNode)
761
+ if (!callNode || !pathNode)
762
+ continue;
763
+ if (!okHttpUrlRootsAtBuilder(callNode))
715
764
  continue;
716
765
  const path = unquoteLiteral(pathNode.text);
717
766
  if (path === null)
718
767
  continue;
768
+ const method = inferOkHttpMethod(callNode);
769
+ // An explicit `.method(verb, …)` with a non-literal or empty verb is
770
+ // unresolvable (null) — emit nothing rather than a wrong GET or an empty
771
+ // `http::::/path` contract.
772
+ if (!method)
773
+ continue;
719
774
  out.push({
720
775
  role: 'consumer',
721
776
  framework: 'okhttp',
722
- method: 'GET',
777
+ method,
723
778
  path,
724
779
  name: null,
725
780
  confidence: 0.7,
726
781
  });
727
782
  }
728
783
  // ─── Consumers: Java HttpClient request builder ─────────────────
729
- // Java's builder exposes GET/POST/PUT/DELETE helpers. PATCH uses
730
- // `.method("PATCH", body)`, which is intentionally deferred.
784
+ // Match any `.uri(URI.create("..."))`, gate to chains including `HttpRequest`
785
+ // `.newBuilder()` (so a call before `.uri()` is captured but an unrelated
786
+ // `.uri()` is not); `inferHttpClientMethod` scans the chain for the verb — a
787
+ // `.GET()/.POST()/.PUT()/.DELETE()/.HEAD()` helper, a `.method("VERB", body)`
788
+ // literal, or the bare-build default GET. A variable-bound `.method(verb, …)`
789
+ // is unresolvable → emit nothing. Mirrors the OkHttp loop above. The
790
+ // constructor-arg form (`newBuilder(URI.create(...))`, no `.uri()`) follows.
731
791
  for (const match of runCompiledPatterns(JAVA_HTTP_CLIENT_PATTERNS, tree)) {
732
- const httpMethodNode = match.captures.http_method;
792
+ const callNode = match.captures.call;
733
793
  const pathNode = match.captures.path;
734
- if (!httpMethodNode || !pathNode)
794
+ if (!callNode || !pathNode)
795
+ continue;
796
+ if (!httpClientUriRootsAtNewBuilder(callNode))
735
797
  continue;
736
798
  const path = unquoteLiteral(pathNode.text);
737
799
  if (path === null)
738
800
  continue;
801
+ const method = inferHttpClientMethod(callNode);
802
+ if (!method)
803
+ continue;
739
804
  out.push({
740
805
  role: 'consumer',
741
806
  framework: 'java-http-client',
742
- method: httpMethodNode.text.toUpperCase(),
807
+ method,
808
+ path,
809
+ name: null,
810
+ confidence: 0.65,
811
+ });
812
+ }
813
+ // Constructor-arg form: `HttpRequest.newBuilder(URI.create("..."))` with the
814
+ // path in the constructor and no overriding `.uri(...)` later in the chain
815
+ // (a later `.uri()` wins at runtime, so the loop above owns that case).
816
+ for (const match of runCompiledPatterns(JAVA_HTTP_CLIENT_CTOR_PATTERNS, tree)) {
817
+ const callNode = match.captures.call;
818
+ const pathNode = match.captures.path;
819
+ if (!callNode || !pathNode)
820
+ continue;
821
+ if (httpClientChainHasUriCall(callNode))
822
+ continue;
823
+ const path = unquoteLiteral(pathNode.text);
824
+ if (path === null)
825
+ continue;
826
+ const method = inferHttpClientMethod(callNode);
827
+ if (!method)
828
+ continue;
829
+ out.push({
830
+ role: 'consumer',
831
+ framework: 'java-http-client',
832
+ method,
743
833
  path,
744
834
  name: null,
745
835
  confidence: 0.65,
@@ -95,6 +95,119 @@ catch {
95
95
  const arrayOfArg = (cap) => `(call_expression
96
96
  (simple_identifier) @arrayOf (#eq? @arrayOf "arrayOf")
97
97
  (call_suffix (value_arguments (value_argument (string_literal) ${cap}))))`;
98
+ // ─── Kotlin OkHttp builder verb-walk (parity with java-static-path.ts) ──
99
+ // Mirrors `inferOkHttpMethod`, adapted to the Kotlin grammar: a call `X.name(args)`
100
+ // is a `call_expression` whose callee is a `navigation_expression` (receiver +
101
+ // `navigation_suffix` → the method name) and whose `call_suffix` holds the
102
+ // `value_arguments`. The chain is left-nested via `navigation_expression`, so we
103
+ // walk UP from the matched `.url(...)` call to the sibling verb call (`.post()` /
104
+ // `.method("X")`), exactly as the Java side does — so `.java` and `.kt` infer the
105
+ // same verb for the same OkHttp shape.
106
+ const OK_HTTP_VERB_HELPERS = ['get', 'head', 'post', 'put', 'delete', 'patch'];
107
+ /** The method name a Kotlin `call_expression` invokes (its `navigation_suffix`). */
108
+ function kotlinCallName(call) {
109
+ const callee = call.namedChild(0);
110
+ if (callee?.type !== 'navigation_expression')
111
+ return null;
112
+ for (let i = 0; i < callee.namedChildCount; i++) {
113
+ const child = callee.namedChild(i);
114
+ if (child?.type === 'navigation_suffix')
115
+ return child.namedChild(0)?.text ?? null;
116
+ }
117
+ return null;
118
+ }
119
+ /** The first string-literal argument of a Kotlin `call_expression`, else null. */
120
+ function kotlinFirstStringArg(call) {
121
+ for (let i = 0; i < call.namedChildCount; i++) {
122
+ const callSuffix = call.namedChild(i);
123
+ if (callSuffix?.type !== 'call_suffix')
124
+ continue;
125
+ for (let j = 0; j < callSuffix.namedChildCount; j++) {
126
+ const args = callSuffix.namedChild(j);
127
+ if (args?.type !== 'value_arguments')
128
+ continue;
129
+ const firstArg = args.namedChild(0);
130
+ if (firstArg?.type !== 'value_argument')
131
+ return null;
132
+ // Positional literal `"X"`, or named-argument `name = "X"` (the label is a
133
+ // leading `simple_identifier` and the literal follows). A non-literal value
134
+ // (a variable) → null → unresolvable.
135
+ const positional = firstArg.namedChild(0);
136
+ if (positional?.type === 'string_literal')
137
+ return unquoteLiteral(positional.text);
138
+ const labeled = positional?.type === 'simple_identifier' ? firstArg.namedChild(1) : null;
139
+ return labeled?.type === 'string_literal' ? unquoteLiteral(labeled.text) : null;
140
+ }
141
+ }
142
+ return null;
143
+ }
144
+ /** The receiver expression a Kotlin `call_expression` is invoked on. */
145
+ function kotlinReceiver(call) {
146
+ const nav = call.namedChild(0);
147
+ return nav?.type === 'navigation_expression' ? nav.namedChild(0) : null;
148
+ }
149
+ /** The call that invokes a method ON `call` as its receiver — one hop up the chain. */
150
+ function kotlinChainParentCall(call) {
151
+ const nav = call.parent;
152
+ if (nav?.type !== 'navigation_expression' || nav.namedChild(0)?.id !== call.id)
153
+ return null;
154
+ return nav.parent?.type === 'call_expression' ? nav.parent : null;
155
+ }
156
+ /** Every `call_expression` in the fluent chain `pathCall` belongs to, innermost
157
+ * (next to the construction) → outermost. Lets the verb-walk find a verb call
158
+ * wherever it sits relative to `.url(...)` — parity with java-static-path.ts
159
+ * `builderChainCalls`. */
160
+ function kotlinBuilderChainCalls(pathCall) {
161
+ let innermost = pathCall;
162
+ let recv = kotlinReceiver(innermost);
163
+ while (recv?.type === 'call_expression') {
164
+ innermost = recv;
165
+ recv = kotlinReceiver(innermost);
166
+ }
167
+ const calls = [innermost];
168
+ let cur = innermost;
169
+ for (let next = kotlinChainParentCall(cur); next; next = kotlinChainParentCall(cur)) {
170
+ calls.push(next);
171
+ cur = next;
172
+ }
173
+ return calls;
174
+ }
175
+ /** True when `urlCall`'s receiver chain bottoms out on `Request.Builder()` — the
176
+ * Kotlin anti-overreach gate (mirror of okHttpUrlRootsAtBuilder), so a `.url(...)`
177
+ * on an unrelated object is rejected while a builder call before `.url()` is kept. */
178
+ function kotlinUrlRootsAtRequestBuilder(urlCall) {
179
+ let cur = kotlinReceiver(urlCall);
180
+ while (cur?.type === 'call_expression') {
181
+ const recv = kotlinReceiver(cur);
182
+ if (recv?.type === 'simple_identifier')
183
+ return recv.text === 'Request' && kotlinCallName(cur) === 'Builder';
184
+ cur = recv;
185
+ }
186
+ return false;
187
+ }
188
+ /** Infer the OkHttp verb by scanning the builder chain around the matched
189
+ * `.url(...)` call — parity with java-static-path.ts `inferOkHttpMethod`. The
190
+ * LAST verb call wins (runtime overwrite); `null` for an unresolvable
191
+ * `.method(verb)` (non-literal/empty) so the caller skips; `'GET'` when the
192
+ * chain has no verb call. */
193
+ function inferKotlinOkHttpMethod(urlCall) {
194
+ let lastVerbCall = null;
195
+ for (const call of kotlinBuilderChainCalls(urlCall)) {
196
+ if (call.id === urlCall.id)
197
+ continue;
198
+ const name = kotlinCallName(call);
199
+ if (name === 'method' || (name !== null && OK_HTTP_VERB_HELPERS.includes(name)))
200
+ lastVerbCall = call;
201
+ }
202
+ if (lastVerbCall === null)
203
+ return 'GET'; // no verb call → OkHttp default
204
+ const name = kotlinCallName(lastVerbCall);
205
+ if (name === 'method') {
206
+ const verb = kotlinFirstStringArg(lastVerbCall);
207
+ return verb ? verb.toUpperCase() : null;
208
+ }
209
+ return name === null ? 'GET' : name.toUpperCase();
210
+ }
98
211
  /**
99
212
  * Build the plugin only if the Kotlin grammar is available. Compiling
100
213
  * the queries against a null grammar would throw at module load time
@@ -382,26 +495,25 @@ function buildKotlinPlugin(language) {
382
495
  ],
383
496
  });
384
497
  // ─── Consumer: OkHttp Request.Builder().url("/x") ─────────────────────
385
- // Kotlin parses `Request.Builder()` as a `call_expression` whose
386
- // callee is a `navigation_expression` (Request .Builder), NOT as
387
- // Java's `object_creation_expression`. The chain `.url("/x")` then
388
- // wraps that in another `call_expression`. The query mirrors Java's
389
- // `OK_HTTP_PATTERNS` (java.ts) but adapts the node types.
498
+ // Full parity with the Java OkHttp extraction (java.ts + java-static-path.ts),
499
+ // adapted to the Kotlin grammar (a call is a `call_expression` whose callee is a
500
+ // `navigation_expression`, not Java's `object_creation_expression`):
390
501
  //
391
- // Receiver `Request` is constrained by name (#eq? @cls); a project
392
- // that imports OkHttp's `Request` under an alias (`import okhttp3.Request as OkRequest`)
393
- // would not be picked up this matches the Java plugin's heuristic.
502
+ // Match a bare `.url("literal")` call on ANY receiver (capture it as `@call`);
503
+ // `kotlinUrlRootsAtRequestBuilder` (JS) then verifies the receiver chain
504
+ // bottoms out on `Request.Builder()`re-imposing the framework anchor while
505
+ // allowing a builder call BEFORE `.url()` (`Request.Builder().addHeader(...)`
506
+ // `.url(...)`) and rejecting a `.url(...)` on an unrelated object.
507
+ // • `inferKotlinOkHttpMethod` scans the whole chain for the verb (`.post(body)`
508
+ // / `.get()` / `.method("X")`, before or after `.url()`) — the mirror of
509
+ // `inferOkHttpMethod`. So `Request.Builder().url("/x").post(body).build()`
510
+ // becomes `http::POST::/x` on both `.java` and `.kt` (pinned by the Java↔Kotlin
511
+ // parity harness). A variable-bound/empty `.method(verb)` is unresolvable →
512
+ // the call is skipped (not a guessed GET), matching the Java side.
394
513
  //
395
- // **Known limitation verb defaults to GET.** OkHttp encodes the
396
- // verb on a *sibling* call further down the builder chain (e.g.
397
- // `.post(body)` / `.get()` / `.delete()`), not on `.url(...)` itself.
398
- // This query intentionally does not walk the chain to recover the
399
- // verb — it emits `method: 'GET'` for every match, mirroring
400
- // `java.ts:OK_HTTP_PATTERNS`. So a `Request.Builder().url("/x").post(body).build()`
401
- // call becomes `http::GET::/x`, not `http::POST::/x`. This is the
402
- // same trade-off Java has accepted; pinned by an anti-overreach
403
- // test in `http-route-extractor.test.ts` so a future verb-walk
404
- // implementation has to update this comment in lockstep.
514
+ // Receiver `Request` is constrained by name (in the JS gate); a project that
515
+ // imports OkHttp's `Request` under an alias would not be picked up — matching the
516
+ // Java plugin's heuristic.
405
517
  const OK_HTTP_PATTERNS = compilePatterns({
406
518
  name: 'kotlin-okhttp',
407
519
  language,
@@ -411,14 +523,9 @@ function buildKotlinPlugin(language) {
411
523
  query: `
412
524
  (call_expression
413
525
  (navigation_expression
414
- (call_expression
415
- (navigation_expression
416
- (simple_identifier) @cls (#eq? @cls "Request")
417
- (navigation_suffix (simple_identifier) @builder (#eq? @builder "Builder")))
418
- (call_suffix (value_arguments)))
419
526
  (navigation_suffix (simple_identifier) @method (#eq? @method "url")))
420
527
  (call_suffix
421
- (value_arguments . (value_argument . (string_literal) @path))))
528
+ (value_arguments . (value_argument . (string_literal) @path)))) @call
422
529
  `,
423
530
  },
424
531
  ],
@@ -945,17 +1052,27 @@ function buildKotlinPlugin(language) {
945
1052
  });
946
1053
  }
947
1054
  // ─── Consumers: OkHttp Request.Builder().url("path") ────────────
1055
+ // Gate to chains rooting at `Request.Builder()` (so a call before `.url()` is
1056
+ // captured but an unrelated `.url()` is not), then recover the verb by scanning
1057
+ // the chain (parity with Java); a variable-bound or empty `.method(verb)` is
1058
+ // unresolvable → emit nothing rather than a GET.
948
1059
  for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
1060
+ const callNode = match.captures.call;
949
1061
  const pathNode = match.captures.path;
950
- if (!pathNode)
1062
+ if (!callNode || !pathNode)
1063
+ continue;
1064
+ if (!kotlinUrlRootsAtRequestBuilder(callNode))
951
1065
  continue;
952
1066
  const path = unquoteLiteral(pathNode.text);
953
1067
  if (path === null)
954
1068
  continue;
1069
+ const method = inferKotlinOkHttpMethod(callNode);
1070
+ if (!method)
1071
+ continue;
955
1072
  out.push({
956
1073
  role: 'consumer',
957
1074
  framework: 'okhttp',
958
- method: 'GET',
1075
+ method,
959
1076
  path,
960
1077
  name: null,
961
1078
  confidence: 0.7,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.4",
3
+ "version": "1.6.9-rc.5",
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",