gitnexus 1.6.9-rc.2 → 1.6.9-rc.3

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,137 @@
1
+ /**
2
+ * Shared, language-agnostic primitives for Spring / OpenFeign / Spring-HTTP-Interface
3
+ * HTTP *consumer* extraction, used by BOTH the Java (`java.ts`) and Kotlin
4
+ * (`kotlin.ts`) group-layer HTTP plugins so the two cannot drift apart.
5
+ *
6
+ * These are pure value maps + string helpers — no tree-sitter AST knowledge.
7
+ * Each language plugin keeps its own grammar-specific queries and walkers and
8
+ * funnels the extracted (verb, path, prefix) facts through these helpers, so a
9
+ * polyglot repo emits byte-identical contract IDs from `.java` and `.kt`.
10
+ *
11
+ * The provider-side annotation→verb map (`METHOD_ANNOTATION_TO_HTTP`),
12
+ * `isRouteMemberKey`, and `findEnclosingClass` live in the lower
13
+ * `ingestion/route-extractors/spring-shared.ts` (shared with the ingestion
14
+ * route extractor). This module is the consumer-side counterpart and lives in
15
+ * the group layer beside the plugins that use it.
16
+ */
17
+ import type { HttpFileDetections } from './types.js';
18
+ /**
19
+ * RestTemplate method-name → HTTP verb. Source-scan only: the receiver must be
20
+ * named exactly `restTemplate` (the per-language query enforces that).
21
+ */
22
+ export declare const REST_TEMPLATE_TO_HTTP: Record<string, string>;
23
+ /**
24
+ * Reactive WebClient short-form verb helper → HTTP verb
25
+ * (`webClient.get().uri("/x")`, `.post()`, ...). HEAD/OPTIONS/TRACE are
26
+ * intentionally excluded for symmetry across the plugins.
27
+ */
28
+ export declare const WEB_CLIENT_SHORT_TO_HTTP: Record<string, string>;
29
+ /**
30
+ * Accepted HTTP verbs for the WebClient long form
31
+ * `webClient.method(HttpMethod.X).uri("/y")`. The verb is captured as the
32
+ * literal `HttpMethod.X` field name (`GET`, `POST`, …); HEAD/OPTIONS/TRACE are
33
+ * intentionally excluded for symmetry with the short form
34
+ * (`WEB_CLIENT_SHORT_TO_HTTP`). Shared so the Java and Kotlin long-form scans
35
+ * gate verbs identically.
36
+ */
37
+ export declare const WEB_CLIENT_LONG_VERB_RE: RegExp;
38
+ /**
39
+ * Spring 6 declarative HTTP Interface shortcut annotation → HTTP verb.
40
+ *
41
+ * `@GetExchange`/`@PostExchange`/… on a service interface proxied by
42
+ * `HttpServiceProxyFactory` (over RestClient / WebClient / RestTemplate)
43
+ * describe an OUTBOUND call — i.e. a CONSUMER, the modern analogue of an
44
+ * OpenFeign `@(Get|Post|...)Mapping` interface method. The path lives in the
45
+ * annotation's `url` (or `value`) attribute, or positionally.
46
+ *
47
+ * The base `@HttpExchange(method = "GET", url = "...")` form carries its verb
48
+ * in an attribute rather than the annotation name; the shortcut annotations
49
+ * above are the overwhelmingly common case and the only ones mapped here.
50
+ */
51
+ export declare const EXCHANGE_ANNOTATION_TO_HTTP: Record<string, string>;
52
+ /**
53
+ * Accumulate a route prefix (de-duped) under a class/interface declaration node
54
+ * id. A Spring route attribute is `String[]`; a multi-element array (`["/a","/b"]`,
55
+ * `arrayOf("/a","/b")`, `{"/a","/b"}`) yields one query match per element, so
56
+ * prefixes accumulate rather than overwrite. Shared by the Java and Kotlin plugins
57
+ * so both build their prefix maps identically.
58
+ */
59
+ export declare const pushPrefix: (map: Map<number, string[]>, id: number, prefix: string) => void;
60
+ /** Framework tags emitted on consumer detections (stable contract metadata). */
61
+ export declare const OPENFEIGN_FRAMEWORK = "openfeign";
62
+ export declare const HTTP_INTERFACE_FRAMEWORK = "spring-http-interface";
63
+ /** Consumer-detection confidences, shared so `.java` and `.kt` agree. */
64
+ export declare const FEIGN_CONFIDENCE = 0.7;
65
+ export declare const REQUEST_LINE_CONFIDENCE = 0.75;
66
+ export declare const EXCHANGE_CONFIDENCE = 0.75;
67
+ /**
68
+ * OpenFeign's native `@RequestLine("METHOD /path[?query]")` packs an HTTP
69
+ * method and path in a single string literal — see
70
+ * https://github.com/OpenFeign/feign#interface-annotations. This regex splits
71
+ * the verb from the path of that literal.
72
+ */
73
+ export declare const REQUEST_LINE_VERB_RE: RegExp;
74
+ /**
75
+ * Parse a Feign `@RequestLine` value into a method + path pair. The query
76
+ * portion is dropped because contract IDs are method+path only (consistent
77
+ * with how RestTemplate/WebClient consumers drop inline query strings).
78
+ *
79
+ * Returns null if the value is not a recognized HTTP verb followed by a path
80
+ * beginning with `/`.
81
+ */
82
+ export declare function parseRequestLine(raw: string): {
83
+ method: string;
84
+ path: string;
85
+ } | null;
86
+ /**
87
+ * Join a class/interface-level prefix and a method-level path into a single
88
+ * URL path: strip leading/trailing slashes on the prefix and leading slashes
89
+ * on the method path, then ensure exactly one slash between them.
90
+ */
91
+ export declare function joinPath(prefix: string, methodPath: string): string;
92
+ /**
93
+ * Join a controller's own class prefix with a route inherited from an interface
94
+ * (interface-based controllers, #1743). The inherited path already has the
95
+ * interface's own class prefix (`inheritedOwnerPrefix`) baked in; when the
96
+ * controller repeats that same prefix we must NOT prepend it twice (#2057).
97
+ * Shared by both plugins' `scanProject` so Java and Kotlin agree.
98
+ */
99
+ export declare function joinInheritedSpringPath(controllerPrefix: string, inheritedPath: string, inheritedOwnerPrefix?: string): string;
100
+ /**
101
+ * Language-agnostic view of a Spring class/interface that each plugin's
102
+ * grammar-specific collector produces. The interface-based-controller
103
+ * inheritance algorithm (`scanSpringInheritanceProject`) operates only on this
104
+ * shape, so the Java and Kotlin plugins share one algorithm and cannot drift.
105
+ *
106
+ * `methods[].routes` carry only `{ method, path }` — the interface's own class
107
+ * prefix is applied *inside* `scanSpringInheritanceProject` (it is not part of
108
+ * the collector's output).
109
+ */
110
+ export interface SharedSpringType {
111
+ filePath: string;
112
+ kind: 'class' | 'interface';
113
+ name: string;
114
+ /** Class-level `@RequestMapping` prefixes — one per array element. */
115
+ classPrefixes: string[];
116
+ implementedInterfaces: string[];
117
+ isController: boolean;
118
+ methods: Array<{
119
+ name: string;
120
+ routes: Array<{
121
+ method: string;
122
+ path: string;
123
+ }>;
124
+ }>;
125
+ }
126
+ /**
127
+ * Resolve interface-based-controller provider routes (#1743): a concrete
128
+ * `@RestController`/`@Controller` class inherits the `@(Get|...)Mapping` routes
129
+ * declared on the interface it implements. Shared by the Java and Kotlin plugins
130
+ * so both emit byte-identical provider contracts.
131
+ *
132
+ * An interface name that resolves to two distinct interfaces is ambiguous and
133
+ * its routes are dropped (the `null` marker). The controller's own class
134
+ * prefix(es) cross-product the inherited routes; `joinInheritedSpringPath`
135
+ * avoids doubling a prefix the interface already baked in (#2057).
136
+ */
137
+ export declare function scanSpringInheritanceProject(types: SharedSpringType[]): HttpFileDetections[];
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Shared, language-agnostic primitives for Spring / OpenFeign / Spring-HTTP-Interface
3
+ * HTTP *consumer* extraction, used by BOTH the Java (`java.ts`) and Kotlin
4
+ * (`kotlin.ts`) group-layer HTTP plugins so the two cannot drift apart.
5
+ *
6
+ * These are pure value maps + string helpers — no tree-sitter AST knowledge.
7
+ * Each language plugin keeps its own grammar-specific queries and walkers and
8
+ * funnels the extracted (verb, path, prefix) facts through these helpers, so a
9
+ * polyglot repo emits byte-identical contract IDs from `.java` and `.kt`.
10
+ *
11
+ * The provider-side annotation→verb map (`METHOD_ANNOTATION_TO_HTTP`),
12
+ * `isRouteMemberKey`, and `findEnclosingClass` live in the lower
13
+ * `ingestion/route-extractors/spring-shared.ts` (shared with the ingestion
14
+ * route extractor). This module is the consumer-side counterpart and lives in
15
+ * the group layer beside the plugins that use it.
16
+ */
17
+ /**
18
+ * RestTemplate method-name → HTTP verb. Source-scan only: the receiver must be
19
+ * named exactly `restTemplate` (the per-language query enforces that).
20
+ */
21
+ export const REST_TEMPLATE_TO_HTTP = {
22
+ getForObject: 'GET',
23
+ getForEntity: 'GET',
24
+ postForObject: 'POST',
25
+ postForEntity: 'POST',
26
+ put: 'PUT',
27
+ delete: 'DELETE',
28
+ patchForObject: 'PATCH',
29
+ };
30
+ /**
31
+ * Reactive WebClient short-form verb helper → HTTP verb
32
+ * (`webClient.get().uri("/x")`, `.post()`, ...). HEAD/OPTIONS/TRACE are
33
+ * intentionally excluded for symmetry across the plugins.
34
+ */
35
+ export const WEB_CLIENT_SHORT_TO_HTTP = {
36
+ get: 'GET',
37
+ post: 'POST',
38
+ put: 'PUT',
39
+ delete: 'DELETE',
40
+ patch: 'PATCH',
41
+ };
42
+ /**
43
+ * Accepted HTTP verbs for the WebClient long form
44
+ * `webClient.method(HttpMethod.X).uri("/y")`. The verb is captured as the
45
+ * literal `HttpMethod.X` field name (`GET`, `POST`, …); HEAD/OPTIONS/TRACE are
46
+ * intentionally excluded for symmetry with the short form
47
+ * (`WEB_CLIENT_SHORT_TO_HTTP`). Shared so the Java and Kotlin long-form scans
48
+ * gate verbs identically.
49
+ */
50
+ export const WEB_CLIENT_LONG_VERB_RE = /^(GET|POST|PUT|DELETE|PATCH)$/;
51
+ /**
52
+ * Spring 6 declarative HTTP Interface shortcut annotation → HTTP verb.
53
+ *
54
+ * `@GetExchange`/`@PostExchange`/… on a service interface proxied by
55
+ * `HttpServiceProxyFactory` (over RestClient / WebClient / RestTemplate)
56
+ * describe an OUTBOUND call — i.e. a CONSUMER, the modern analogue of an
57
+ * OpenFeign `@(Get|Post|...)Mapping` interface method. The path lives in the
58
+ * annotation's `url` (or `value`) attribute, or positionally.
59
+ *
60
+ * The base `@HttpExchange(method = "GET", url = "...")` form carries its verb
61
+ * in an attribute rather than the annotation name; the shortcut annotations
62
+ * above are the overwhelmingly common case and the only ones mapped here.
63
+ */
64
+ export const EXCHANGE_ANNOTATION_TO_HTTP = {
65
+ GetExchange: 'GET',
66
+ PostExchange: 'POST',
67
+ PutExchange: 'PUT',
68
+ DeleteExchange: 'DELETE',
69
+ PatchExchange: 'PATCH',
70
+ };
71
+ /**
72
+ * Accumulate a route prefix (de-duped) under a class/interface declaration node
73
+ * id. A Spring route attribute is `String[]`; a multi-element array (`["/a","/b"]`,
74
+ * `arrayOf("/a","/b")`, `{"/a","/b"}`) yields one query match per element, so
75
+ * prefixes accumulate rather than overwrite. Shared by the Java and Kotlin plugins
76
+ * so both build their prefix maps identically.
77
+ */
78
+ export const pushPrefix = (map, id, prefix) => {
79
+ const arr = map.get(id) ?? [];
80
+ if (!arr.includes(prefix))
81
+ arr.push(prefix);
82
+ map.set(id, arr);
83
+ };
84
+ /** Framework tags emitted on consumer detections (stable contract metadata). */
85
+ export const OPENFEIGN_FRAMEWORK = 'openfeign';
86
+ export const HTTP_INTERFACE_FRAMEWORK = 'spring-http-interface';
87
+ /** Consumer-detection confidences, shared so `.java` and `.kt` agree. */
88
+ export const FEIGN_CONFIDENCE = 0.7;
89
+ export const REQUEST_LINE_CONFIDENCE = 0.75;
90
+ export const EXCHANGE_CONFIDENCE = 0.75;
91
+ /**
92
+ * OpenFeign's native `@RequestLine("METHOD /path[?query]")` packs an HTTP
93
+ * method and path in a single string literal — see
94
+ * https://github.com/OpenFeign/feign#interface-annotations. This regex splits
95
+ * the verb from the path of that literal.
96
+ */
97
+ export const REQUEST_LINE_VERB_RE = /^\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\S.*?)\s*$/i;
98
+ /**
99
+ * Parse a Feign `@RequestLine` value into a method + path pair. The query
100
+ * portion is dropped because contract IDs are method+path only (consistent
101
+ * with how RestTemplate/WebClient consumers drop inline query strings).
102
+ *
103
+ * Returns null if the value is not a recognized HTTP verb followed by a path
104
+ * beginning with `/`.
105
+ */
106
+ export function parseRequestLine(raw) {
107
+ const match = REQUEST_LINE_VERB_RE.exec(raw);
108
+ if (!match)
109
+ return null;
110
+ const [, verb, rest] = match;
111
+ if (typeof verb !== 'string' || typeof rest !== 'string')
112
+ return null;
113
+ const queryIdx = rest.indexOf('?');
114
+ const pathOnly = (queryIdx >= 0 ? rest.slice(0, queryIdx) : rest).trim();
115
+ if (!pathOnly.startsWith('/'))
116
+ return null;
117
+ return { method: verb.toUpperCase(), path: pathOnly };
118
+ }
119
+ /**
120
+ * Join a class/interface-level prefix and a method-level path into a single
121
+ * URL path: strip leading/trailing slashes on the prefix and leading slashes
122
+ * on the method path, then ensure exactly one slash between them.
123
+ */
124
+ export function joinPath(prefix, methodPath) {
125
+ const cleanPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
126
+ const cleanSub = methodPath.replace(/^\/+/, '');
127
+ if (!cleanPrefix)
128
+ return `/${cleanSub}`;
129
+ return `/${cleanPrefix}/${cleanSub}`;
130
+ }
131
+ /**
132
+ * Join a controller's own class prefix with a route inherited from an interface
133
+ * (interface-based controllers, #1743). The inherited path already has the
134
+ * interface's own class prefix (`inheritedOwnerPrefix`) baked in; when the
135
+ * controller repeats that same prefix we must NOT prepend it twice (#2057).
136
+ * Shared by both plugins' `scanProject` so Java and Kotlin agree.
137
+ */
138
+ export function joinInheritedSpringPath(controllerPrefix, inheritedPath, inheritedOwnerPrefix = '') {
139
+ const joined = joinPath(controllerPrefix, inheritedPath);
140
+ const cleanPrefix = controllerPrefix.replace(/^\/+/, '').replace(/\/+$/, '');
141
+ const cleanOwnerPrefix = inheritedOwnerPrefix.replace(/^\/+/, '').replace(/\/+$/, '');
142
+ const cleanInherited = inheritedPath.replace(/^\/+/, '');
143
+ if (!cleanPrefix)
144
+ return joined;
145
+ if (cleanPrefix === cleanOwnerPrefix &&
146
+ (cleanInherited === cleanPrefix || cleanInherited.startsWith(`${cleanPrefix}/`))) {
147
+ return `/${cleanInherited}`;
148
+ }
149
+ return joined;
150
+ }
151
+ /**
152
+ * Resolve interface-based-controller provider routes (#1743): a concrete
153
+ * `@RestController`/`@Controller` class inherits the `@(Get|...)Mapping` routes
154
+ * declared on the interface it implements. Shared by the Java and Kotlin plugins
155
+ * so both emit byte-identical provider contracts.
156
+ *
157
+ * An interface name that resolves to two distinct interfaces is ambiguous and
158
+ * its routes are dropped (the `null` marker). The controller's own class
159
+ * prefix(es) cross-product the inherited routes; `joinInheritedSpringPath`
160
+ * avoids doubling a prefix the interface already baked in (#2057).
161
+ */
162
+ export function scanSpringInheritanceProject(types) {
163
+ const interfaceRoutes = new Map();
164
+ for (const type of types) {
165
+ if (type.kind !== 'interface')
166
+ continue;
167
+ if (interfaceRoutes.has(type.name)) {
168
+ interfaceRoutes.set(type.name, null);
169
+ continue;
170
+ }
171
+ const prefixes = type.classPrefixes.length ? type.classPrefixes : [''];
172
+ const methodMap = new Map();
173
+ for (const method of type.methods) {
174
+ // Cross-product the interface's class prefixes with each method route, so a
175
+ // multi-element `@RequestMapping(["/a","/b"])` interface yields N bindings.
176
+ const routes = method.routes.flatMap((route) => prefixes.map((prefix) => ({
177
+ method: route.method,
178
+ path: prefix ? joinPath(prefix, route.path) : route.path,
179
+ ownerPrefix: prefix,
180
+ })));
181
+ if (routes.length > 0)
182
+ methodMap.set(method.name, routes);
183
+ }
184
+ interfaceRoutes.set(type.name, methodMap);
185
+ }
186
+ const detectionsByFile = new Map();
187
+ for (const type of types) {
188
+ if (type.kind !== 'class' || !type.isController)
189
+ continue;
190
+ // Cross-product the controller's own class prefixes with each inherited
191
+ // route; `['']` keeps the common no-prefix controller emitting the
192
+ // interface path unchanged.
193
+ const controllerPrefixes = type.classPrefixes.length ? type.classPrefixes : [''];
194
+ for (const method of type.methods) {
195
+ if (method.routes.length > 0)
196
+ continue; // own @*Mapping → already a provider via scan()
197
+ const inherited = type.implementedInterfaces.flatMap((iface) => {
198
+ const routeMap = interfaceRoutes.get(iface);
199
+ if (!routeMap)
200
+ return [];
201
+ const routes = routeMap.get(method.name) ?? [];
202
+ return routes.flatMap((route) => controllerPrefixes.map((controllerPrefix) => ({
203
+ method: route.method,
204
+ path: joinInheritedSpringPath(controllerPrefix, route.path, route.ownerPrefix),
205
+ })));
206
+ });
207
+ const seen = new Set();
208
+ for (const route of inherited) {
209
+ const key = `${route.method} ${route.path}`;
210
+ if (seen.has(key))
211
+ continue;
212
+ seen.add(key);
213
+ const detections = detectionsByFile.get(type.filePath) ?? [];
214
+ detections.push({
215
+ role: 'provider',
216
+ framework: 'spring',
217
+ method: route.method,
218
+ path: route.path,
219
+ name: method.name,
220
+ confidence: 0.8,
221
+ });
222
+ detectionsByFile.set(type.filePath, detections);
223
+ }
224
+ }
225
+ }
226
+ return [...detectionsByFile.entries()].map(([filePath, detections]) => ({
227
+ filePath,
228
+ detections,
229
+ }));
230
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.2",
3
+ "version": "1.6.9-rc.3",
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",