gitnexus 1.6.0 → 1.6.2-rc.1

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 (145) hide show
  1. package/README.md +73 -0
  2. package/dist/cli/analyze.js +50 -3
  3. package/dist/core/group/extractors/fs-utils.d.ts +10 -0
  4. package/dist/core/group/extractors/fs-utils.js +24 -0
  5. package/dist/core/group/extractors/grpc-extractor.d.ts +17 -8
  6. package/dist/core/group/extractors/grpc-extractor.js +328 -191
  7. package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
  8. package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
  9. package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
  10. package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
  11. package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
  12. package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
  13. package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
  14. package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
  15. package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
  16. package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
  17. package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
  18. package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
  19. package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
  20. package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
  21. package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
  22. package/dist/core/group/extractors/http-patterns/go.js +215 -0
  23. package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
  24. package/dist/core/group/extractors/http-patterns/index.js +44 -0
  25. package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
  26. package/dist/core/group/extractors/http-patterns/java.js +253 -0
  27. package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
  28. package/dist/core/group/extractors/http-patterns/node.js +354 -0
  29. package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
  30. package/dist/core/group/extractors/http-patterns/php.js +70 -0
  31. package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
  32. package/dist/core/group/extractors/http-patterns/python.js +133 -0
  33. package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
  34. package/dist/core/group/extractors/http-patterns/types.js +1 -0
  35. package/dist/core/group/extractors/http-route-extractor.d.ts +10 -13
  36. package/dist/core/group/extractors/http-route-extractor.js +231 -238
  37. package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
  38. package/dist/core/group/extractors/manifest-extractor.js +277 -0
  39. package/dist/core/group/extractors/topic-extractor.d.ts +0 -1
  40. package/dist/core/group/extractors/topic-extractor.js +55 -192
  41. package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
  42. package/dist/core/group/extractors/topic-patterns/go.js +120 -0
  43. package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
  44. package/dist/core/group/extractors/topic-patterns/index.js +38 -0
  45. package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
  46. package/dist/core/group/extractors/topic-patterns/java.js +80 -0
  47. package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
  48. package/dist/core/group/extractors/topic-patterns/node.js +155 -0
  49. package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
  50. package/dist/core/group/extractors/topic-patterns/python.js +116 -0
  51. package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
  52. package/dist/core/group/extractors/topic-patterns/types.js +10 -0
  53. package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
  54. package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
  55. package/dist/core/ingestion/binding-accumulator.d.ts +22 -17
  56. package/dist/core/ingestion/binding-accumulator.js +29 -25
  57. package/dist/core/ingestion/cobol-processor.d.ts +1 -1
  58. package/dist/core/ingestion/import-processor.js +1 -1
  59. package/dist/core/ingestion/language-config.js +1 -1
  60. package/dist/core/ingestion/language-provider.d.ts +32 -5
  61. package/dist/core/ingestion/languages/c-cpp.js +2 -2
  62. package/dist/core/ingestion/languages/dart.d.ts +1 -1
  63. package/dist/core/ingestion/languages/dart.js +2 -2
  64. package/dist/core/ingestion/languages/go.d.ts +1 -1
  65. package/dist/core/ingestion/languages/go.js +2 -2
  66. package/dist/core/ingestion/languages/ruby.js +16 -1
  67. package/dist/core/ingestion/languages/swift.d.ts +1 -1
  68. package/dist/core/ingestion/languages/swift.js +2 -2
  69. package/dist/core/ingestion/markdown-processor.d.ts +1 -1
  70. package/dist/core/ingestion/method-extractors/configs/jvm.js +1 -0
  71. package/dist/core/ingestion/method-extractors/configs/ruby.js +1 -0
  72. package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
  73. package/dist/core/ingestion/method-extractors/generic.js +48 -4
  74. package/dist/core/ingestion/method-types.d.ts +4 -0
  75. package/dist/core/ingestion/model/resolve.js +103 -48
  76. package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
  77. package/dist/core/ingestion/model/semantic-model.js +1 -1
  78. package/dist/core/ingestion/model/symbol-table.d.ts +7 -7
  79. package/dist/core/ingestion/model/symbol-table.js +7 -7
  80. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  81. package/dist/core/ingestion/mro-processor.js +1 -1
  82. package/dist/core/ingestion/parsing-processor.js +54 -42
  83. package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
  84. package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
  85. package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
  86. package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
  87. package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
  88. package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
  89. package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
  90. package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
  91. package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
  92. package/dist/core/ingestion/pipeline-phases/index.js +22 -0
  93. package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
  94. package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
  95. package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
  96. package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
  97. package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
  98. package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
  99. package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
  100. package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
  101. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
  102. package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
  103. package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
  104. package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
  105. package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
  106. package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
  107. package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
  108. package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
  109. package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
  110. package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
  111. package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
  112. package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
  113. package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
  114. package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
  115. package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
  116. package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
  117. package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
  118. package/dist/core/ingestion/pipeline-phases/types.js +37 -0
  119. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +70 -0
  120. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +312 -0
  121. package/dist/core/ingestion/pipeline.d.ts +16 -10
  122. package/dist/core/ingestion/pipeline.js +66 -1534
  123. package/dist/core/ingestion/process-processor.js +1 -1
  124. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  125. package/dist/core/ingestion/tree-sitter-queries.js +69 -0
  126. package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -3
  127. package/dist/core/ingestion/utils/ast-helpers.js +48 -21
  128. package/dist/core/ingestion/utils/env.d.ts +10 -0
  129. package/dist/core/ingestion/utils/env.js +10 -0
  130. package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
  131. package/dist/core/ingestion/utils/graph-sort.js +100 -0
  132. package/dist/core/ingestion/workers/parse-worker.js +12 -8
  133. package/dist/core/lbug/lbug-adapter.d.ts +28 -0
  134. package/dist/core/lbug/lbug-adapter.js +162 -57
  135. package/package.json +3 -3
  136. package/vendor/tree-sitter-proto/binding.gyp +30 -0
  137. package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
  138. package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
  139. package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
  140. package/vendor/tree-sitter-proto/package.json +18 -0
  141. package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
  142. package/vendor/tree-sitter-proto/src/parser.c +10149 -0
  143. package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
  144. package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
  145. package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
@@ -0,0 +1,354 @@
1
+ import JavaScript from 'tree-sitter-javascript';
2
+ import TypeScript from 'tree-sitter-typescript';
3
+ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
4
+ /**
5
+ * Node.js / TypeScript HTTP plugin family. Handles:
6
+ * - NestJS `@Controller('prefix')` classes with `@Get(':id')` methods
7
+ * - Express `router.get(...)` / `app.post(...)` providers
8
+ * - `fetch(url)` / `fetch(url, { method: 'POST' })` consumers
9
+ * - `axios.get(url)` / `axios.delete(url)` consumers
10
+ *
11
+ * Because the JavaScript and TypeScript tree-sitter grammars share
12
+ * node type names for every construct we query, pattern sources are
13
+ * defined once and compiled against each grammar variant. The plugin
14
+ * exports three `HttpLanguagePlugin`s (JS, TS, TSX) that share the
15
+ * same `scan` function but bind to different grammars.
16
+ */
17
+ // ─── Provider: NestJS — class-level @Controller('prefix') ────────────
18
+ // In tree-sitter-typescript decorators are NOT children of
19
+ // class_declaration / method_definition — they're siblings in the
20
+ // surrounding class_body / program node. We therefore match the
21
+ // decorator standalone and walk to its related class/method in JS.
22
+ const NEST_CONTROLLER_SPEC = {
23
+ meta: {},
24
+ query: `
25
+ (decorator
26
+ (call_expression
27
+ function: (identifier) @dec (#eq? @dec "Controller")
28
+ arguments: (arguments . [(string) (template_string)] @prefix))) @ctrl_decorator
29
+ `,
30
+ };
31
+ // ─── Provider: NestJS — method-level @Get/@Post/... decorators ───────
32
+ // Matches either `@Get('path')` or `@Get()`. The `@path` capture is
33
+ // optional — when the first argument isn't a string, the plugin falls
34
+ // back to '/' for the method-level path.
35
+ const NEST_METHOD_SPEC = {
36
+ meta: {},
37
+ query: `
38
+ (decorator
39
+ (call_expression
40
+ function: (identifier) @dec (#match? @dec "^(Get|Post|Put|Delete|Patch)$")
41
+ arguments: (arguments) @args)) @method_decorator
42
+ `,
43
+ };
44
+ // ─── Provider: Express — router.get/app.post/... ─────────────────────
45
+ const EXPRESS_SPEC = {
46
+ meta: {},
47
+ query: `
48
+ (call_expression
49
+ function: (member_expression
50
+ object: (identifier) @obj (#match? @obj "^(router|app)$")
51
+ property: (property_identifier) @http_method (#match? @http_method "^(get|post|put|delete|patch)$"))
52
+ arguments: (arguments . [(string) (template_string)] @path))
53
+ `,
54
+ };
55
+ // ─── Consumer: fetch(url) with NO options ─────────────────────────────
56
+ const FETCH_NO_OPTIONS_SPEC = {
57
+ meta: {},
58
+ query: `
59
+ (call_expression
60
+ function: (identifier) @fn (#eq? @fn "fetch")
61
+ arguments: (arguments . [(string) (template_string)] @path .))
62
+ `,
63
+ };
64
+ // ─── Consumer: fetch(url, { method: 'X', ... }) ──────────────────────
65
+ const FETCH_WITH_OPTIONS_SPEC = {
66
+ meta: {},
67
+ query: `
68
+ (call_expression
69
+ function: (identifier) @fn (#eq? @fn "fetch")
70
+ arguments: (arguments
71
+ . [(string) (template_string)] @path
72
+ (object
73
+ (pair
74
+ key: (property_identifier) @key (#eq? @key "method")
75
+ value: (string) @http_method))))
76
+ `,
77
+ };
78
+ // ─── Consumer: axios.get/post/... ────────────────────────────────────
79
+ const AXIOS_SPEC = {
80
+ meta: {},
81
+ query: `
82
+ (call_expression
83
+ function: (member_expression
84
+ object: (identifier) @obj (#eq? @obj "axios")
85
+ property: (property_identifier) @http_method (#match? @http_method "^(get|post|put|delete|patch)$"))
86
+ arguments: (arguments . [(string) (template_string)] @path))
87
+ `,
88
+ };
89
+ function compileBundle(language, name) {
90
+ const mk = (spec, suffix) => compilePatterns({
91
+ name: `${name}-${suffix}`,
92
+ language,
93
+ patterns: [spec],
94
+ });
95
+ return {
96
+ controller: mk(NEST_CONTROLLER_SPEC, 'nest-controller'),
97
+ methodDecorator: mk(NEST_METHOD_SPEC, 'nest-method-decorator'),
98
+ express: mk(EXPRESS_SPEC, 'express'),
99
+ fetchNoOptions: mk(FETCH_NO_OPTIONS_SPEC, 'fetch-no-options'),
100
+ fetchWithOptions: mk(FETCH_WITH_OPTIONS_SPEC, 'fetch-with-options'),
101
+ axios: mk(AXIOS_SPEC, 'axios'),
102
+ };
103
+ }
104
+ const JAVASCRIPT_BUNDLE = compileBundle(JavaScript, 'javascript-http');
105
+ const TYPESCRIPT_BUNDLE = compileBundle(TypeScript.typescript, 'typescript-http');
106
+ const TSX_BUNDLE = compileBundle(TypeScript.tsx, 'tsx-http');
107
+ const NEST_DECORATOR_TO_HTTP = {
108
+ Get: 'GET',
109
+ Post: 'POST',
110
+ Put: 'PUT',
111
+ Delete: 'DELETE',
112
+ Patch: 'PATCH',
113
+ };
114
+ /**
115
+ * Find the nearest enclosing class_declaration for a node, or null.
116
+ */
117
+ function findEnclosingClass(node) {
118
+ let cur = node.parent;
119
+ while (cur) {
120
+ if (cur.type === 'class_declaration')
121
+ return cur;
122
+ cur = cur.parent;
123
+ }
124
+ return null;
125
+ }
126
+ function joinPath(prefix, sub) {
127
+ const cleanPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
128
+ const cleanSub = sub.replace(/^\/+/, '');
129
+ if (!cleanPrefix)
130
+ return `/${cleanSub}`;
131
+ return `/${cleanPrefix}/${cleanSub}`;
132
+ }
133
+ /**
134
+ * For a standalone `decorator` node (child of class_body / program),
135
+ * find the related `class_declaration` node that it decorates. In
136
+ * tree-sitter-typescript the decorator is placed before the class
137
+ * declaration as a sibling (when decorating a class) or inside the
138
+ * class_body before a method_definition (when decorating a method);
139
+ * we walk the parent chain until we find the enclosing class.
140
+ */
141
+ function findDecoratedClass(decoratorNode) {
142
+ const parent = decoratorNode.parent;
143
+ if (!parent)
144
+ return null;
145
+ // Case 1: decorator is a sibling of the class_declaration at program /
146
+ // export_statement level. Walk forward through siblings until we find
147
+ // the class_declaration this decorator belongs to.
148
+ for (let i = 0; i < parent.namedChildCount; i++) {
149
+ const child = parent.namedChild(i);
150
+ if (child && child.id === decoratorNode.id) {
151
+ for (let j = i + 1; j < parent.namedChildCount; j++) {
152
+ const next = parent.namedChild(j);
153
+ if (!next)
154
+ continue;
155
+ if (next.type === 'decorator')
156
+ continue; // adjacent decorators stack
157
+ if (next.type === 'class_declaration')
158
+ return next;
159
+ if (next.type === 'export_statement') {
160
+ // `export class Foo { ... }` wraps the declaration.
161
+ for (let k = 0; k < next.namedChildCount; k++) {
162
+ const inner = next.namedChild(k);
163
+ if (inner?.type === 'class_declaration')
164
+ return inner;
165
+ }
166
+ }
167
+ break;
168
+ }
169
+ break;
170
+ }
171
+ }
172
+ // Case 2: decorator is inside a class_body (decorating a method) —
173
+ // walk up to the enclosing class_declaration.
174
+ return findEnclosingClass(decoratorNode);
175
+ }
176
+ /**
177
+ * For a method-level decorator node (child of class_body before a
178
+ * method_definition), find the method_definition it decorates.
179
+ */
180
+ function findDecoratedMethod(decoratorNode) {
181
+ const parent = decoratorNode.parent;
182
+ if (!parent || parent.type !== 'class_body')
183
+ return null;
184
+ for (let i = 0; i < parent.namedChildCount; i++) {
185
+ const child = parent.namedChild(i);
186
+ if (child && child.id === decoratorNode.id) {
187
+ for (let j = i + 1; j < parent.namedChildCount; j++) {
188
+ const next = parent.namedChild(j);
189
+ if (!next)
190
+ continue;
191
+ if (next.type === 'decorator')
192
+ continue;
193
+ if (next.type === 'method_definition')
194
+ return next;
195
+ return null;
196
+ }
197
+ return null;
198
+ }
199
+ }
200
+ return null;
201
+ }
202
+ function scanBundle(bundle, tree) {
203
+ const out = [];
204
+ // NestJS: collect `@Controller('prefix')` class decorators, keyed by
205
+ // the `class_declaration` they decorate.
206
+ const prefixByClassId = new Map();
207
+ for (const match of runCompiledPatterns(bundle.controller, tree)) {
208
+ const prefixNode = match.captures.prefix;
209
+ const decoratorNode = match.captures.ctrl_decorator;
210
+ if (!prefixNode || !decoratorNode)
211
+ continue;
212
+ const prefix = unquoteLiteral(prefixNode.text);
213
+ if (prefix === null)
214
+ continue;
215
+ const classNode = findDecoratedClass(decoratorNode);
216
+ if (!classNode)
217
+ continue;
218
+ prefixByClassId.set(classNode.id, prefix);
219
+ }
220
+ // NestJS: method-level @Get/@Post/... decorators. The decorator's
221
+ // arguments list may be empty (`@Get()`), a string (`@Get('path')`),
222
+ // or something else (which we skip).
223
+ for (const match of runCompiledPatterns(bundle.methodDecorator, tree)) {
224
+ const decNode = match.captures.dec;
225
+ const argsNode = match.captures.args;
226
+ const decoratorNode = match.captures.method_decorator;
227
+ if (!decNode || !argsNode || !decoratorNode)
228
+ continue;
229
+ const httpMethod = NEST_DECORATOR_TO_HTTP[decNode.text];
230
+ if (!httpMethod)
231
+ continue;
232
+ const methodNode = findDecoratedMethod(decoratorNode);
233
+ if (!methodNode)
234
+ continue;
235
+ const enclosingClass = findEnclosingClass(methodNode);
236
+ // Only emit NestJS detections when the class actually has a
237
+ // @Controller decorator — without it, the match is almost certainly
238
+ // something else (e.g. an unrelated library using similar names).
239
+ if (!enclosingClass || !prefixByClassId.has(enclosingClass.id))
240
+ continue;
241
+ const prefix = prefixByClassId.get(enclosingClass.id) ?? '';
242
+ let rawPath = '/';
243
+ const firstArg = argsNode.namedChild(0);
244
+ if (firstArg && (firstArg.type === 'string' || firstArg.type === 'template_string')) {
245
+ const unquoted = unquoteLiteral(firstArg.text);
246
+ if (unquoted !== null)
247
+ rawPath = unquoted;
248
+ }
249
+ // Get the method name from the decorated method_definition.
250
+ const methodNameNode = methodNode.childForFieldName('name');
251
+ const name = methodNameNode?.text ?? null;
252
+ out.push({
253
+ role: 'provider',
254
+ framework: 'nest',
255
+ method: httpMethod,
256
+ path: joinPath(prefix, rawPath),
257
+ name,
258
+ confidence: 0.8,
259
+ });
260
+ }
261
+ // Express: router/app.<verb>(...)
262
+ for (const match of runCompiledPatterns(bundle.express, tree)) {
263
+ const methodNode = match.captures.http_method;
264
+ const pathNode = match.captures.path;
265
+ if (!methodNode || !pathNode)
266
+ continue;
267
+ const path = unquoteLiteral(pathNode.text);
268
+ if (path === null)
269
+ continue;
270
+ out.push({
271
+ role: 'provider',
272
+ framework: 'express',
273
+ method: methodNode.text.toUpperCase(),
274
+ path,
275
+ name: 'handler',
276
+ confidence: 0.8,
277
+ });
278
+ }
279
+ // Consumer: fetch with options { method: 'X' }
280
+ const fetchSeen = new Set();
281
+ for (const match of runCompiledPatterns(bundle.fetchWithOptions, tree)) {
282
+ const pathNode = match.captures.path;
283
+ const methodNode = match.captures.http_method;
284
+ if (!pathNode || !methodNode)
285
+ continue;
286
+ const path = unquoteLiteral(pathNode.text);
287
+ const method = unquoteLiteral(methodNode.text);
288
+ if (path === null || method === null)
289
+ continue;
290
+ fetchSeen.add(pathNode.id);
291
+ out.push({
292
+ role: 'consumer',
293
+ framework: 'fetch',
294
+ method: method.toUpperCase(),
295
+ path,
296
+ name: null,
297
+ confidence: 0.7,
298
+ });
299
+ }
300
+ // Consumer: plain fetch(path) — default GET. Skip path nodes we already
301
+ // matched with the options variant so we don't double-emit.
302
+ for (const match of runCompiledPatterns(bundle.fetchNoOptions, tree)) {
303
+ const pathNode = match.captures.path;
304
+ if (!pathNode)
305
+ continue;
306
+ if (fetchSeen.has(pathNode.id))
307
+ continue;
308
+ const path = unquoteLiteral(pathNode.text);
309
+ if (path === null)
310
+ continue;
311
+ out.push({
312
+ role: 'consumer',
313
+ framework: 'fetch',
314
+ method: 'GET',
315
+ path,
316
+ name: null,
317
+ confidence: 0.7,
318
+ });
319
+ }
320
+ // Consumer: axios.<verb>(url)
321
+ for (const match of runCompiledPatterns(bundle.axios, tree)) {
322
+ const methodNode = match.captures.http_method;
323
+ const pathNode = match.captures.path;
324
+ if (!methodNode || !pathNode)
325
+ continue;
326
+ const path = unquoteLiteral(pathNode.text);
327
+ if (path === null)
328
+ continue;
329
+ out.push({
330
+ role: 'consumer',
331
+ framework: 'axios',
332
+ method: methodNode.text.toUpperCase(),
333
+ path,
334
+ name: null,
335
+ confidence: 0.7,
336
+ });
337
+ }
338
+ return out;
339
+ }
340
+ export const JAVASCRIPT_HTTP_PLUGIN = {
341
+ name: 'javascript-http',
342
+ language: JavaScript,
343
+ scan: (tree) => scanBundle(JAVASCRIPT_BUNDLE, tree),
344
+ };
345
+ export const TYPESCRIPT_HTTP_PLUGIN = {
346
+ name: 'typescript-http',
347
+ language: TypeScript.typescript,
348
+ scan: (tree) => scanBundle(TYPESCRIPT_BUNDLE, tree),
349
+ };
350
+ export const TSX_HTTP_PLUGIN = {
351
+ name: 'tsx-http',
352
+ language: TypeScript.tsx,
353
+ scan: (tree) => scanBundle(TSX_BUNDLE, tree),
354
+ };
@@ -0,0 +1,2 @@
1
+ import type { HttpLanguagePlugin } from './types.js';
2
+ export declare const PHP_HTTP_PLUGIN: HttpLanguagePlugin;
@@ -0,0 +1,70 @@
1
+ import PHP from 'tree-sitter-php';
2
+ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
3
+ /**
4
+ * PHP HTTP plugin — Laravel `Route::get/post/...` declarations.
5
+ *
6
+ * The pipeline already uses `PHP.php_only` for ingesting plain `.php`
7
+ * files (see `core/tree-sitter/parser-loader.ts`), and we do the same
8
+ * here so Laravel route files are parsed with the right grammar dialect.
9
+ */
10
+ const LARAVEL_PATTERNS = compilePatterns({
11
+ name: 'php-laravel',
12
+ language: PHP.php_only,
13
+ patterns: [
14
+ {
15
+ meta: {},
16
+ query: `
17
+ (scoped_call_expression
18
+ scope: (name) @scope (#eq? @scope "Route")
19
+ name: (name) @method (#match? @method "^(get|post|put|delete|patch)$")
20
+ arguments: (arguments . (argument (string) @path)))
21
+ `,
22
+ },
23
+ ],
24
+ });
25
+ /**
26
+ * Extract the inner text of a PHP `string` node. The tree-sitter-php
27
+ * grammar wraps single / double-quoted literals differently depending
28
+ * on content; we try both the raw `text` (with quotes) through
29
+ * `unquoteLiteral`, and a fallback via the `string_value` / `string_content`
30
+ * child nodes.
31
+ */
32
+ function phpStringText(node) {
33
+ // Most single-quoted strings expose their inner content through the
34
+ // full node text (including quotes), which unquoteLiteral strips.
35
+ const direct = unquoteLiteral(node.text);
36
+ if (direct !== null && direct !== node.text)
37
+ return direct;
38
+ // Fall back to child string_content / string_value node if present.
39
+ for (const child of node.children) {
40
+ if (child.type === 'string_content' || child.type === 'string_value') {
41
+ return child.text;
42
+ }
43
+ }
44
+ return direct;
45
+ }
46
+ export const PHP_HTTP_PLUGIN = {
47
+ name: 'php-http',
48
+ language: PHP.php_only,
49
+ scan(tree) {
50
+ const out = [];
51
+ for (const match of runCompiledPatterns(LARAVEL_PATTERNS, tree)) {
52
+ const methodNode = match.captures.method;
53
+ const pathNode = match.captures.path;
54
+ if (!methodNode || !pathNode)
55
+ continue;
56
+ const path = phpStringText(pathNode);
57
+ if (path === null)
58
+ continue;
59
+ out.push({
60
+ role: 'provider',
61
+ framework: 'laravel',
62
+ method: methodNode.text.toUpperCase(),
63
+ path,
64
+ name: 'route',
65
+ confidence: 0.8,
66
+ });
67
+ }
68
+ return out;
69
+ },
70
+ };
@@ -0,0 +1,2 @@
1
+ import type { HttpLanguagePlugin } from './types.js';
2
+ export declare const PYTHON_HTTP_PLUGIN: HttpLanguagePlugin;
@@ -0,0 +1,133 @@
1
+ import Python from 'tree-sitter-python';
2
+ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
3
+ /**
4
+ * Python HTTP plugin. Handles:
5
+ * - FastAPI `@app.get("/path")` provider decorators
6
+ * - `requests.get/post/...("url")` consumer calls
7
+ * - Generic `requests.request("METHOD", "url")` consumer calls
8
+ */
9
+ const FASTAPI_VERBS = {
10
+ get: 'GET',
11
+ post: 'POST',
12
+ put: 'PUT',
13
+ delete: 'DELETE',
14
+ patch: 'PATCH',
15
+ };
16
+ // ─── Provider: FastAPI @app.get/... ──────────────────────────────────
17
+ const FASTAPI_PATTERNS = compilePatterns({
18
+ name: 'python-fastapi',
19
+ language: Python,
20
+ patterns: [
21
+ {
22
+ meta: {},
23
+ query: `
24
+ (decorator
25
+ (call
26
+ function: (attribute
27
+ object: (identifier) @obj (#eq? @obj "app")
28
+ attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
29
+ arguments: (argument_list . (string) @path)))
30
+ `,
31
+ },
32
+ ],
33
+ });
34
+ // ─── Consumer: requests.get/post/... ──────────────────────────────────
35
+ const REQUESTS_VERB_PATTERNS = compilePatterns({
36
+ name: 'python-requests-verb',
37
+ language: Python,
38
+ patterns: [
39
+ {
40
+ meta: {},
41
+ query: `
42
+ (call
43
+ function: (attribute
44
+ object: (identifier) @obj (#eq? @obj "requests")
45
+ attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
46
+ arguments: (argument_list . (string) @path))
47
+ `,
48
+ },
49
+ ],
50
+ });
51
+ // ─── Consumer: requests.request("METHOD", "url") ─────────────────────
52
+ const REQUESTS_GENERIC_PATTERNS = compilePatterns({
53
+ name: 'python-requests-generic',
54
+ language: Python,
55
+ patterns: [
56
+ {
57
+ meta: {},
58
+ query: `
59
+ (call
60
+ function: (attribute
61
+ object: (identifier) @obj (#eq? @obj "requests")
62
+ attribute: (identifier) @method (#eq? @method "request"))
63
+ arguments: (argument_list . (string) @http_method (string) @path))
64
+ `,
65
+ },
66
+ ],
67
+ });
68
+ export const PYTHON_HTTP_PLUGIN = {
69
+ name: 'python-http',
70
+ language: Python,
71
+ scan(tree) {
72
+ const out = [];
73
+ // Providers: FastAPI
74
+ for (const match of runCompiledPatterns(FASTAPI_PATTERNS, tree)) {
75
+ const methodNode = match.captures.method;
76
+ const pathNode = match.captures.path;
77
+ if (!methodNode || !pathNode)
78
+ continue;
79
+ const httpMethod = FASTAPI_VERBS[methodNode.text];
80
+ if (!httpMethod)
81
+ continue;
82
+ const path = unquoteLiteral(pathNode.text);
83
+ if (path === null)
84
+ continue;
85
+ out.push({
86
+ role: 'provider',
87
+ framework: 'fastapi',
88
+ method: httpMethod,
89
+ path,
90
+ name: null,
91
+ confidence: 0.8,
92
+ });
93
+ }
94
+ // Consumers: requests.<verb>
95
+ for (const match of runCompiledPatterns(REQUESTS_VERB_PATTERNS, tree)) {
96
+ const methodNode = match.captures.method;
97
+ const pathNode = match.captures.path;
98
+ if (!methodNode || !pathNode)
99
+ continue;
100
+ const path = unquoteLiteral(pathNode.text);
101
+ if (path === null)
102
+ continue;
103
+ out.push({
104
+ role: 'consumer',
105
+ framework: 'python-requests',
106
+ method: methodNode.text.toUpperCase(),
107
+ path,
108
+ name: null,
109
+ confidence: 0.7,
110
+ });
111
+ }
112
+ // Consumers: requests.request("METHOD", "url")
113
+ for (const match of runCompiledPatterns(REQUESTS_GENERIC_PATTERNS, tree)) {
114
+ const methodNode = match.captures.http_method;
115
+ const pathNode = match.captures.path;
116
+ if (!methodNode || !pathNode)
117
+ continue;
118
+ const methodRaw = unquoteLiteral(methodNode.text);
119
+ const path = unquoteLiteral(pathNode.text);
120
+ if (methodRaw === null || path === null)
121
+ continue;
122
+ out.push({
123
+ role: 'consumer',
124
+ framework: 'python-requests',
125
+ method: methodRaw.toUpperCase(),
126
+ path,
127
+ name: null,
128
+ confidence: 0.7,
129
+ });
130
+ }
131
+ return out;
132
+ },
133
+ };
@@ -0,0 +1,61 @@
1
+ import type Parser from 'tree-sitter';
2
+ /**
3
+ * Shared types for the http-route-extractor language plugins.
4
+ *
5
+ * Each plugin lives in its own file (java.ts, node.ts, ...) and owns
6
+ * the tree-sitter grammar import + queries. The top-level
7
+ * `http-route-extractor.ts` orchestrator only knows about this type
8
+ * module and the plugin registry (`./index.ts`). It MUST NOT import
9
+ * any grammar or query text directly — language-specific knowledge
10
+ * belongs in the plugins.
11
+ */
12
+ export type HttpRole = 'provider' | 'consumer';
13
+ /**
14
+ * One raw HTTP detection produced by a plugin's `scan()` function. The
15
+ * orchestrator converts this into a full `ExtractedContract` by running
16
+ * path normalization and building the contract id.
17
+ *
18
+ * `path` is the raw literal string as it appeared in source (with
19
+ * `${...}` template placeholders still in place); the orchestrator
20
+ * runs the appropriate normalizer for provider vs. consumer paths.
21
+ */
22
+ export interface HttpDetection {
23
+ role: HttpRole;
24
+ /** Short framework label, e.g. `'spring'`, `'nest'`, `'express'`. */
25
+ framework: string;
26
+ /** HTTP method in upper case (`'GET'`, `'POST'`, ...). */
27
+ method: string;
28
+ /** Raw path literal as seen in source (template placeholders intact). */
29
+ path: string;
30
+ /**
31
+ * Symbol name of the handler (for providers) or calling function
32
+ * (for consumers) when the plugin can determine it structurally.
33
+ * Null when no good candidate is available.
34
+ */
35
+ name: string | null;
36
+ /** Confidence in (0, 1]. Source-scan plugins typically use 0.7–0.8. */
37
+ confidence: number;
38
+ }
39
+ /**
40
+ * One language-scoped HTTP plugin. The plugin owns the tree-sitter
41
+ * grammar and the `scan` function that translates a parsed tree into
42
+ * zero or more `HttpDetection`s. Plugins are free to run multiple
43
+ * compiled pattern bundles internally (see the shared scanner's
44
+ * `runCompiledPatterns` helper).
45
+ *
46
+ * `language` is typed as `unknown` for the same reason as
47
+ * `LanguagePatterns.language` in `tree-sitter-scanner.ts` — the
48
+ * grammar modules export different shapes.
49
+ */
50
+ export interface HttpLanguagePlugin {
51
+ /** Human-readable plugin name for diagnostics. */
52
+ name: string;
53
+ /** tree-sitter grammar object (passed to the shared parser). */
54
+ language: unknown;
55
+ /**
56
+ * Scan a parsed tree and return zero or more HTTP detections. Plugins
57
+ * must not throw — they should swallow per-match errors so a single
58
+ * malformed construct does not abort the whole file.
59
+ */
60
+ scan(tree: Parser.Tree): HttpDetection[];
61
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,24 +1,21 @@
1
1
  import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
2
2
  import type { ExtractedContract, RepoHandle } from '../types.js';
3
+ /**
4
+ * Canonicalize a provider-side HTTP path for contract-id generation:
5
+ * - strip query string
6
+ * - lower-case
7
+ * - drop trailing slash
8
+ * - collapse `:id`, `{id}`, `[id]` path params into a single `{param}`
9
+ */
3
10
  export declare function normalizeHttpPath(p: string): string;
4
11
  export declare class HttpRouteExtractor implements ContractExtractor {
5
12
  type: "http";
6
13
  canExtract(_repo: RepoHandle): Promise<boolean>;
7
- extract(dbExecutor: CypherExecutor | null, repoPath: string, repo: RepoHandle): Promise<ExtractedContract[]>;
14
+ extract(dbExecutor: CypherExecutor | null, repoPath: string, _repo: RepoHandle): Promise<ExtractedContract[]>;
15
+ private scanFiles;
8
16
  private extractProvidersGraph;
9
- private inferMethodFromFileScan;
10
17
  private extractProvidersSourceScan;
11
- private dedupeContracts;
12
- private scanSpringProviders;
13
- private scanExpressProviders;
14
- private scanLaravelProviders;
15
- private scanFastApiProviders;
16
- private makeProvider;
17
18
  private extractConsumersGraph;
18
- private inferFetchMethod;
19
19
  private extractConsumersSourceScan;
20
- private scanFetchConsumers;
21
- private templateToPattern;
22
- private scanAxiosConsumers;
23
- private makeConsumer;
20
+ private dedupeContracts;
24
21
  }