graph-indexer 2.1.1 → 3.0.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 (131) hide show
  1. package/CHANGELOG.md +312 -1
  2. package/README.md +236 -312
  3. package/SECURITY.md +53 -58
  4. package/bin/graph-indexer.mjs +251 -0
  5. package/integrations/claude-code/.claude-plugin/plugin.json +9 -0
  6. package/integrations/claude-code/.mcp.json +8 -0
  7. package/integrations/claude-code/agents/code-structure.md +22 -0
  8. package/integrations/claude-code/hooks/gi-hook.mjs +103 -0
  9. package/integrations/claude-code/hooks/hooks.json +16 -0
  10. package/integrations/opencode/graph-indexer.js +129 -0
  11. package/package.json +32 -109
  12. package/src/cli/helper.mjs +67 -0
  13. package/src/cli/hook.mjs +366 -0
  14. package/src/cli/init.mjs +272 -0
  15. package/src/cli/resident.mjs +185 -0
  16. package/src/index/discover.mjs +128 -0
  17. package/src/index/fingerprint.mjs +30 -0
  18. package/src/index/graph.mjs +61 -0
  19. package/src/index/indexer.mjs +476 -0
  20. package/src/index/modules.mjs +332 -0
  21. package/src/index/resolver.mjs +961 -0
  22. package/src/mcp/render.mjs +60 -0
  23. package/src/mcp/server.mjs +147 -0
  24. package/src/mcp/tools.mjs +974 -0
  25. package/src/parse/arity.mjs +153 -0
  26. package/src/parse/extract.mjs +1038 -0
  27. package/src/parse/lang/csharp.mjs +106 -0
  28. package/src/parse/lang/ecma.mjs +438 -0
  29. package/src/parse/lang/go.mjs +194 -0
  30. package/src/parse/lang/jvm.mjs +118 -0
  31. package/src/parse/lang/kotlin.mjs +97 -0
  32. package/src/parse/lang/misc.mjs +115 -0
  33. package/src/parse/lang/native.mjs +126 -0
  34. package/src/parse/lang/python.mjs +153 -0
  35. package/src/parse/lang/rust.mjs +157 -0
  36. package/src/parse/lang/scripting.mjs +219 -0
  37. package/src/parse/languages.mjs +48 -0
  38. package/src/parse/runtime.mjs +56 -0
  39. package/src/query/check.mjs +345 -0
  40. package/src/query/facts.mjs +323 -0
  41. package/src/query/intel.mjs +683 -0
  42. package/src/query/read.mjs +81 -0
  43. package/src/query/testcmd.mjs +130 -0
  44. package/src/query/textsearch.mjs +178 -0
  45. package/src/search/search.mjs +210 -0
  46. package/src/search/tokenize.mjs +188 -0
  47. package/src/store/db.mjs +169 -0
  48. package/src/util/git.mjs +30 -0
  49. package/src/util/paths.mjs +63 -0
  50. package/src/util/regex.mjs +2 -0
  51. package/vendor/grammars/bash.wasm.gz +0 -0
  52. package/vendor/grammars/c.wasm.gz +0 -0
  53. package/vendor/grammars/c_sharp.wasm.gz +0 -0
  54. package/vendor/grammars/cpp.wasm.gz +0 -0
  55. package/vendor/grammars/css.wasm.gz +0 -0
  56. package/vendor/grammars/go.wasm.gz +0 -0
  57. package/vendor/grammars/java.wasm.gz +0 -0
  58. package/vendor/grammars/javascript.wasm.gz +0 -0
  59. package/vendor/grammars/kotlin.wasm.gz +0 -0
  60. package/vendor/grammars/manifest.json +148 -0
  61. package/vendor/grammars/php.wasm.gz +0 -0
  62. package/vendor/grammars/python.wasm.gz +0 -0
  63. package/vendor/grammars/ruby.wasm.gz +0 -0
  64. package/vendor/grammars/rust.wasm.gz +0 -0
  65. package/vendor/grammars/scala.wasm.gz +0 -0
  66. package/vendor/grammars/tsx.wasm.gz +0 -0
  67. package/vendor/grammars/typescript.wasm.gz +0 -0
  68. package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
  69. package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  70. package/cli-ui.mjs +0 -78
  71. package/colbert.mjs +0 -168
  72. package/config.mjs +0 -494
  73. package/daemon-ctl.mjs +0 -175
  74. package/daemon-lock.mjs +0 -69
  75. package/embedders/python/mlx_embed_server.py +0 -61
  76. package/embedders/python/requirements-mlx.txt +0 -5
  77. package/embedders/setup-mlx.mjs +0 -110
  78. package/embeddings.mjs +0 -571
  79. package/engine/binary.mjs +0 -437
  80. package/engine/memory.mjs +0 -1003
  81. package/engine/sqlite.mjs +0 -1136
  82. package/enrichment.mjs +0 -521
  83. package/git-signals.mjs +0 -248
  84. package/indexer.mjs +0 -546
  85. package/init.mjs +0 -1809
  86. package/layout.mjs +0 -123
  87. package/mcp/centrality.mjs +0 -117
  88. package/mcp/format.mjs +0 -61
  89. package/mcp/resolver.mjs +0 -116
  90. package/mcp/symbolgraph.mjs +0 -137
  91. package/mcp/taint.mjs +0 -225
  92. package/mcp/tools.mjs +0 -1582
  93. package/mcp/topology.mjs +0 -676
  94. package/mcp-server.mjs +0 -197
  95. package/parse/extractor.mjs +0 -628
  96. package/parse/imports.mjs +0 -365
  97. package/parse/interprocedural.mjs +0 -134
  98. package/parse/languages.mjs +0 -253
  99. package/parse/metadata.mjs +0 -701
  100. package/parse/routes.mjs +0 -327
  101. package/parse/scip.mjs +0 -391
  102. package/parse/taint-patterns.mjs +0 -119
  103. package/prompts/CORE.md +0 -91
  104. package/prompts/DOMAIN_TEMPLATE.md +0 -46
  105. package/prompts/INTEGRATION.md +0 -123
  106. package/prompts/frameworks/ANDROID.md +0 -17
  107. package/prompts/frameworks/ASPNET_CORE.md +0 -19
  108. package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
  109. package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
  110. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
  111. package/prompts/frameworks/RAILS.md +0 -20
  112. package/prompts/frameworks/REACT.md +0 -20
  113. package/prompts/frameworks/SPRING_BOOT.md +0 -20
  114. package/prompts/languages/BASH.md +0 -22
  115. package/prompts/languages/C.md +0 -21
  116. package/prompts/languages/CSHARP.md +0 -24
  117. package/prompts/languages/CSS_SCSS.md +0 -22
  118. package/prompts/languages/GO.md +0 -24
  119. package/prompts/languages/JAVA.md +0 -23
  120. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
  121. package/prompts/languages/KOTLIN.md +0 -24
  122. package/prompts/languages/PHP.md +0 -23
  123. package/prompts/languages/PYTHON.md +0 -23
  124. package/prompts/languages/RUBY.md +0 -23
  125. package/prompts/languages/RUST.md +0 -21
  126. package/prompts/languages/SWIFT.md +0 -21
  127. package/seal.mjs +0 -400
  128. package/search-core.mjs +0 -777
  129. package/search-sparse.mjs +0 -183
  130. package/storage.mjs +0 -73
  131. package/watch-daemon.mjs +0 -289
package/parse/routes.mjs DELETED
@@ -1,327 +0,0 @@
1
- /**
2
- * @file parse/routes.mjs
3
- * @description HTTP route extraction from AST (Express/NestJS/FastAPI/Flask/Spring Boot).
4
- * @author MaquinaTech <https://github.com/MaquinaTech>
5
- * @copyright (c) 2026 MaquinaTech. All rights reserved.
6
- * @license MIT
7
- */
8
-
9
- // ─── HTTP route extraction ────────────────────────────────────────────────────
10
- // The call graph records that a function was called, but cannot connect an HTTP
11
- // verb+path (`GET /api/users`) to the handler that serves it. extractRoutes mines
12
- // that mapping at index time from the four common shapes:
13
- // • decorator-on-method — NestJS (@Get/@Post on a class method)
14
- // • decorator-on-function — FastAPI/Flask (@app.get / @app.route)
15
- // • annotation-on-method — Spring Boot (@GetMapping / @RequestMapping)
16
- // • functional registration — Express/Koa (router.get('/x', handler))
17
- // Each route resolves its handler to a chunk id (by name, case-sensitive) so an
18
- // agent can jump straight from an endpoint to get_chunk / get_call_graph on the
19
- // handler. No framework names are hardcoded beyond the verb sets — detection is by
20
- // AST node type, mirroring extractDecorators / extractCallSites.
21
-
22
- const HTTP_VERBS = new Set(['get', 'post', 'put', 'delete', 'patch', 'all', 'head', 'options']);
23
- const ROUTE_JS_LIKE = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
24
-
25
- /** Unquote a string-literal node across grammars (JS string_fragment, Python
26
- * string_content, Java string_fragment, …). Returns null for non-string nodes. */
27
- function _routeLiteral(node) {
28
- if (!node) return null;
29
- const STR = new Set(['string', 'template_string', 'string_literal',
30
- 'interpreted_string_literal', 'raw_string_literal']);
31
- if (!STR.has(node.type)) return null;
32
- const frag = node.namedChildren?.find(c => /fragment|content/.test(c.type));
33
- if (frag) return frag.text;
34
- return node.text.replace(/^[`'"]+|[`'"]+$/g, '');
35
- }
36
-
37
- /** Join a controller/class path prefix to a method path. Empty prefix → path as-is
38
- * (spec: "if no Controller prefix, leave it as-is"). Collapses duplicate slashes. */
39
- function _joinRoutePath(prefix, sub) {
40
- prefix = (prefix || '').trim();
41
- sub = (sub || '').trim();
42
- if (!prefix) return sub;
43
- if (!sub) return prefix;
44
- return (prefix.replace(/\/+$/, '') + '/' + sub.replace(/^\/+/, '')).replace(/\/{2,}/g, '/');
45
- }
46
-
47
- /** Decorators that lead a node as siblings (mirrors extractDecorators' sibling walk).
48
- * NestJS method decorators sit as previous siblings inside the class_body. */
49
- function _leadingDecorators(node) {
50
- const out = [];
51
- let prev = node.previousSibling;
52
- while (prev) {
53
- if (prev.type === 'decorator') out.push(prev);
54
- else if (prev.isNamed && prev.type !== 'comment') break;
55
- prev = prev.previousSibling;
56
- }
57
- return out;
58
- }
59
-
60
- /** Parse a TS/JS decorator node into { name, path } — name is the callee (`Get`,
61
- * `Controller`), path is its first string argument (or null for bare `@Get()`). */
62
- function _parseTsDecorator(decoNode) {
63
- const call = decoNode.namedChildren?.find(c => c.type === 'call_expression');
64
- if (!call) {
65
- const id = decoNode.namedChildren?.find(c => c.type === 'identifier');
66
- return { name: id?.text || decoNode.text.replace(/^@/, '').split(/[\s(]/)[0], path: null };
67
- }
68
- const fn = call.childForFieldName?.('function');
69
- let name = '';
70
- if (fn?.type === 'identifier') name = fn.text;
71
- else if (fn?.type === 'member_expression') name = fn.childForFieldName?.('property')?.text || '';
72
- const args = call.childForFieldName?.('arguments');
73
- const firstStr = args?.namedChildren?.find(c => _routeLiteral(c) != null);
74
- return { name, path: _routeLiteral(firstStr) };
75
- }
76
-
77
- /** NestJS: @Get/@Post/… on class methods, with the enclosing
78
- * @Controller(prefix) prepended. */
79
- function _extractNestRoutes(rootNode, emit) {
80
- const stack = [rootNode];
81
- while (stack.length) {
82
- const n = stack.pop();
83
- if (n.type === 'class_declaration' || n.type === 'abstract_class_declaration') {
84
- // For `export class` the @Controller decorator is a leading sibling inside
85
- // export_statement; for bare/abstract class it is a direct child before class_body.
86
- const classDecos = [..._leadingDecorators(n)];
87
- for (let i = 0; i < n.namedChildCount; i++) {
88
- const c = n.namedChild(i);
89
- if (c.type === 'class_body') break;
90
- if (c.type === 'decorator') classDecos.push(c);
91
- }
92
- let prefix = '';
93
- for (const d of classDecos) {
94
- const { name, path } = _parseTsDecorator(d);
95
- if (name === 'Controller' && path != null) { prefix = path; break; }
96
- }
97
- const body = n.childForFieldName?.('body')
98
- || n.namedChildren?.find(c => c.type === 'class_body');
99
- if (body) {
100
- for (let i = 0; i < body.namedChildCount; i++) {
101
- const member = body.namedChild(i);
102
- if (member.type !== 'method_definition') continue;
103
- const handler = member.childForFieldName?.('name')?.text
104
- || member.namedChildren?.find(c => c.type === 'property_identifier')?.text;
105
- for (const d of _leadingDecorators(member)) {
106
- const { name, path } = _parseTsDecorator(d);
107
- if (!name || !HTTP_VERBS.has(name.toLowerCase())) continue;
108
- emit(name, _joinRoutePath(prefix, path), handler, member.startPosition.row + 1, 'nestjs');
109
- }
110
- }
111
- }
112
- }
113
- for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
114
- }
115
- }
116
-
117
- /** Derive a handler name from an Express argument node: a bare identifier, a member
118
- * expression (`ctrl.getThing` → 'getThing', `this.handler` → 'handler'), or a
119
- * `.bind()` call (`handler.bind(this)` → 'handler'). Arrow/inline fn → 'anonymous'. */
120
- function _jsHandlerName(node) {
121
- if (!node) return 'anonymous';
122
- if (node.type === 'identifier') return node.text;
123
- if (node.type === 'member_expression') {
124
- return node.childForFieldName?.('property')?.text || 'anonymous';
125
- }
126
- if (node.type === 'call_expression') {
127
- const callee = node.childForFieldName?.('function');
128
- if (callee?.type === 'member_expression') {
129
- const obj = callee.childForFieldName?.('object');
130
- if (obj?.type === 'identifier') return obj.text; // handler.bind(this) → handler
131
- if (obj?.type === 'member_expression') return obj.childForFieldName?.('property')?.text || 'anonymous';
132
- return callee.childForFieldName?.('property')?.text || 'anonymous';
133
- }
134
- if (callee?.type === 'identifier') return callee.text; // makeHandler() → makeHandler
135
- }
136
- return 'anonymous'; // arrow_function / function / function_expression / unknown
137
- }
138
-
139
- /** Express / Koa: `<router|app>.<verb>(<path>, …, <handler>)` call sites. The route
140
- * path must be rooted ('/…') or a wildcard ('*') — this matches Express/Koa
141
- * convention and rejects look-alike calls (`cache.get('key', cb)`, `map.get(k)`). */
142
- function _extractExpressRoutes(rootNode, emit) {
143
- const stack = [rootNode];
144
- while (stack.length) {
145
- const n = stack.pop();
146
- if (n.type === 'call_expression') {
147
- const fn = n.childForFieldName?.('function');
148
- if (fn?.type === 'member_expression') {
149
- const verb = fn.childForFieldName?.('property')?.text || '';
150
- const args = n.childForFieldName?.('arguments');
151
- if (verb && HTTP_VERBS.has(verb.toLowerCase()) && args) {
152
- const named = args.namedChildren || [];
153
- const routePath = _routeLiteral(named[0]);
154
- if (routePath != null && /^[/*]/.test(routePath) && named.length >= 2) {
155
- // The handler is the LAST argument — preceding args may be middleware.
156
- const handler = _jsHandlerName(named[named.length - 1]);
157
- emit(verb, routePath, handler, n.startPosition.row + 1, 'express');
158
- }
159
- }
160
- }
161
- }
162
- for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
163
- }
164
- }
165
-
166
- /** FastAPI / Flask: @app.get / @router.post / @app.route(path, methods=[…]). */
167
- function _extractPyRoutes(rootNode, emit) {
168
- const stack = [rootNode];
169
- while (stack.length) {
170
- const n = stack.pop();
171
- if (n.type === 'decorated_definition') {
172
- const def = n.namedChildren?.find(c => c.type === 'function_definition');
173
- const handler = def?.childForFieldName?.('name')?.text
174
- || def?.namedChildren?.find(c => c.type === 'identifier')?.text;
175
- const line = (def || n).startPosition.row + 1;
176
- for (const d of n.namedChildren || []) {
177
- if (d.type !== 'decorator') continue;
178
- const call = d.namedChildren?.find(c => c.type === 'call');
179
- const fn = call?.childForFieldName?.('function');
180
- if (fn?.type !== 'attribute') continue;
181
- const verb = fn.childForFieldName?.('attribute')?.text || '';
182
- const args = call.childForFieldName?.('arguments');
183
- // FastAPI uses a positional path string; Flask uses `path=`/`rule=` keyword argument.
184
- let routePath = _routeLiteral(args?.namedChildren?.find(c => c.type === 'string'));
185
- if (routePath == null) {
186
- const kw = args?.namedChildren?.find(c => c.type === 'keyword_argument'
187
- && ['path', 'rule'].includes(c.childForFieldName?.('name')?.text));
188
- routePath = _routeLiteral(kw?.childForFieldName?.('value'));
189
- }
190
- // Require a rooted path string to reject look-alike decorators such as
191
- // `@cache.get("key")` or `@retry.post(n=3)` that share verb names.
192
- const isRoute = routePath != null && routePath.startsWith('/');
193
- if (HTTP_VERBS.has(verb.toLowerCase())) {
194
- if (isRoute) emit(verb, routePath, handler, line, 'fastapi');
195
- } else if (verb === 'route' && isRoute) {
196
- // Flask `@app.route` can declare multiple HTTP methods; default GET when `methods` is omitted.
197
- const kw = args?.namedChildren?.find(c => c.type === 'keyword_argument'
198
- && c.childForFieldName?.('name')?.text === 'methods');
199
- const list = kw?.childForFieldName?.('value');
200
- const methods = list
201
- ? (list.namedChildren || []).map(_routeLiteral).filter(Boolean)
202
- : [];
203
- for (const m of (methods.length ? methods : ['GET'])) {
204
- emit(m, routePath, handler, line, 'flask');
205
- }
206
- }
207
- }
208
- }
209
- for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
210
- }
211
- }
212
-
213
- /** Spring Boot: @GetMapping/@PostMapping/… and @RequestMapping(value=…, method=…)
214
- * on methods, with class-level @RequestMapping as a prefix. */
215
- function _springAnnotation(annNode) {
216
- // annotation > identifier <name> + annotation_argument_list
217
- const name = annNode.namedChildren?.find(c => c.type === 'identifier')?.text || '';
218
- const argList = annNode.namedChildren?.find(c => c.type === 'annotation_argument_list');
219
- const verbOf = (t) => (t || '').split('.').pop(); // RequestMethod.POST → POST
220
- let path = null, methods = [];
221
- if (argList) {
222
- const positional = argList.namedChildren?.find(c => _routeLiteral(c) != null);
223
- if (positional) path = _routeLiteral(positional);
224
- for (const pair of argList.namedChildren || []) {
225
- if (pair.type !== 'element_value_pair') continue;
226
- const key = pair.namedChildren?.[0]?.text;
227
- const valNode = pair.namedChildren?.[1];
228
- if (key === 'value' || key === 'path') path = _routeLiteral(valNode) ?? path;
229
- else if (key === 'method') {
230
- // `method` may be a single value or an array literal — handle both shapes.
231
- methods = (valNode?.type === 'element_value_array_initializer'
232
- ? (valNode.namedChildren || []).map(c => verbOf(c.text))
233
- : [verbOf(valNode?.text)]).filter(Boolean);
234
- }
235
- }
236
- }
237
- return { name, path, methods };
238
- }
239
-
240
- const SPRING_VERB = {
241
- GetMapping: 'GET', PostMapping: 'POST', PutMapping: 'PUT',
242
- DeleteMapping: 'DELETE', PatchMapping: 'PATCH',
243
- };
244
-
245
- function _extractSpringRoutes(rootNode, emit) {
246
- const stack = [rootNode];
247
- while (stack.length) {
248
- const n = stack.pop();
249
- if (n.type === 'class_declaration') {
250
- const mods = n.namedChildren?.find(c => c.type === 'modifiers');
251
- let prefix = '';
252
- for (const a of (mods?.namedChildren || [])) {
253
- if (a.type !== 'annotation') continue;
254
- const { name, path } = _springAnnotation(a);
255
- if (name === 'RequestMapping' && path != null) { prefix = path; break; }
256
- }
257
- const body = n.namedChildren?.find(c => c.type === 'class_body');
258
- for (const member of (body?.namedChildren || [])) {
259
- if (member.type !== 'method_declaration') continue;
260
- const handler = member.childForFieldName?.('name')?.text;
261
- const mMods = member.namedChildren?.find(c => c.type === 'modifiers');
262
- for (const a of (mMods?.namedChildren || [])) {
263
- if (a.type !== 'annotation' && a.type !== 'marker_annotation') continue;
264
- const { name, path, methods } = _springAnnotation(a);
265
- let verbs;
266
- if (SPRING_VERB[name]) verbs = [SPRING_VERB[name]];
267
- else if (name === 'RequestMapping') verbs = methods.length ? methods : ['ALL'];
268
- else continue;
269
- // @RequestMapping(method = {GET, POST}) → one route per verb.
270
- for (const verb of verbs) {
271
- emit(verb, _joinRoutePath(prefix, path), handler, member.startPosition.row + 1, 'spring');
272
- }
273
- }
274
- }
275
- }
276
- for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
277
- }
278
- }
279
-
280
- /**
281
- * Map HTTP routes to their handlers for one parsed file.
282
- *
283
- * @param {object} rootNode parsed AST root
284
- * @param {string} relPath repo-relative file path (stored on each route)
285
- * @param {object[]} chunks the file's already-extracted chunks (handler→id lookup)
286
- * @param {string} ext dotted file extension
287
- * @returns {Array<{method,path,handler_name,handler_chunk_id,file_path,line,framework}>}
288
- */
289
- export function extractRoutes(rootNode, relPath, chunks, ext) {
290
- const routes = [];
291
- if (!rootNode) return routes;
292
-
293
- // Case-sensitive; first definition wins to match the chunk extraction order.
294
- const idByName = new Map();
295
- for (const c of (chunks || [])) {
296
- if (c && c.name && !idByName.has(c.name)) idByName.set(c.name, c.id);
297
- }
298
-
299
- const emit = (method, routePath, handlerName, line, framework) => {
300
- if (!method) return;
301
- // NestJS controller-prefixed paths omit the leading slash ('users/:id'); normalise
302
- // so a '/'-prefix query matches uniformly across all frameworks.
303
- let p = routePath == null ? '' : String(routePath);
304
- if (p && !p.startsWith('/')) p = '/' + p;
305
- routes.push({
306
- method: String(method).toUpperCase(),
307
- path: p,
308
- handler_name: handlerName || 'anonymous',
309
- handler_chunk_id: (handlerName && idByName.has(handlerName)) ? idByName.get(handlerName) : null,
310
- file_path: relPath,
311
- line,
312
- framework,
313
- });
314
- };
315
-
316
- try {
317
- if (ROUTE_JS_LIKE.includes(ext)) {
318
- _extractNestRoutes(rootNode, emit);
319
- _extractExpressRoutes(rootNode, emit);
320
- } else if (ext === '.py') {
321
- _extractPyRoutes(rootNode, emit);
322
- } else if (ext === '.java') {
323
- _extractSpringRoutes(rootNode, emit);
324
- }
325
- } catch { /* best-effort — never fail indexing */ }
326
- return routes;
327
- }