gitnexus 1.3.6 → 1.3.8

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 (47) hide show
  1. package/dist/cli/ai-context.js +77 -23
  2. package/dist/cli/analyze.js +4 -11
  3. package/dist/cli/eval-server.d.ts +7 -0
  4. package/dist/cli/eval-server.js +16 -7
  5. package/dist/cli/index.js +2 -20
  6. package/dist/cli/mcp.js +2 -0
  7. package/dist/cli/setup.js +6 -1
  8. package/dist/config/supported-languages.d.ts +1 -0
  9. package/dist/config/supported-languages.js +1 -0
  10. package/dist/core/ingestion/call-processor.d.ts +5 -1
  11. package/dist/core/ingestion/call-processor.js +78 -0
  12. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  13. package/dist/core/ingestion/framework-detection.js +49 -2
  14. package/dist/core/ingestion/import-processor.js +90 -39
  15. package/dist/core/ingestion/parsing-processor.d.ts +12 -1
  16. package/dist/core/ingestion/parsing-processor.js +92 -51
  17. package/dist/core/ingestion/pipeline.js +21 -2
  18. package/dist/core/ingestion/process-processor.js +0 -1
  19. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
  20. package/dist/core/ingestion/tree-sitter-queries.js +80 -0
  21. package/dist/core/ingestion/utils.d.ts +5 -0
  22. package/dist/core/ingestion/utils.js +20 -0
  23. package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
  24. package/dist/core/ingestion/workers/parse-worker.js +473 -51
  25. package/dist/core/kuzu/csv-generator.d.ts +4 -0
  26. package/dist/core/kuzu/csv-generator.js +23 -9
  27. package/dist/core/kuzu/kuzu-adapter.js +9 -3
  28. package/dist/core/tree-sitter/parser-loader.d.ts +1 -0
  29. package/dist/core/tree-sitter/parser-loader.js +3 -0
  30. package/dist/mcp/core/kuzu-adapter.d.ts +4 -3
  31. package/dist/mcp/core/kuzu-adapter.js +79 -16
  32. package/dist/mcp/local/local-backend.d.ts +13 -0
  33. package/dist/mcp/local/local-backend.js +148 -105
  34. package/dist/mcp/server.js +26 -11
  35. package/dist/storage/git.js +4 -1
  36. package/dist/storage/repo-manager.js +16 -2
  37. package/hooks/claude/gitnexus-hook.cjs +28 -8
  38. package/hooks/claude/pre-tool-use.sh +2 -1
  39. package/package.json +11 -3
  40. package/dist/cli/claude-hooks.d.ts +0 -22
  41. package/dist/cli/claude-hooks.js +0 -97
  42. package/dist/cli/view.d.ts +0 -13
  43. package/dist/cli/view.js +0 -59
  44. package/dist/core/graph/html-graph-viewer.d.ts +0 -15
  45. package/dist/core/graph/html-graph-viewer.js +0 -542
  46. package/dist/core/graph/html-graph-viewer.test.d.ts +0 -1
  47. package/dist/core/graph/html-graph-viewer.test.js +0 -67
@@ -9,9 +9,11 @@ import CPP from 'tree-sitter-cpp';
9
9
  import CSharp from 'tree-sitter-c-sharp';
10
10
  import Go from 'tree-sitter-go';
11
11
  import Rust from 'tree-sitter-rust';
12
+ import Kotlin from 'tree-sitter-kotlin';
12
13
  import PHP from 'tree-sitter-php';
13
14
  import { createRequire } from 'node:module';
14
15
  import { SupportedLanguages } from '../../../config/supported-languages.js';
16
+ import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
15
17
  // tree-sitter-swift is an optionalDependency — may not be installed
16
18
  const _require = createRequire(import.meta.url);
17
19
  let Swift = null;
@@ -19,8 +21,7 @@ try {
19
21
  Swift = _require('tree-sitter-swift');
20
22
  }
21
23
  catch { }
22
- import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
23
- import { getLanguageFromFilename } from '../utils.js';
24
+ import { findSiblingChild, getLanguageFromFilename } from '../utils.js';
24
25
  import { detectFrameworkFromAST } from '../framework-detection.js';
25
26
  import { generateId } from '../../../lib/utils.js';
26
27
  // ============================================================================
@@ -38,6 +39,7 @@ const languageMap = {
38
39
  [SupportedLanguages.CSharp]: CSharp,
39
40
  [SupportedLanguages.Go]: Go,
40
41
  [SupportedLanguages.Rust]: Rust,
42
+ [SupportedLanguages.Kotlin]: Kotlin,
41
43
  [SupportedLanguages.PHP]: PHP.php_only,
42
44
  ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
43
45
  };
@@ -115,18 +117,26 @@ const isNodeExported = (node, name, language) => {
115
117
  current = current.parent;
116
118
  }
117
119
  return false;
118
- case 'c':
119
- case 'cpp':
120
- return false;
121
- case 'swift':
120
+ // Kotlin: Default visibility is public (unlike Java)
121
+ // visibility_modifier is inside modifiers, a sibling of the name node within the declaration
122
+ case 'kotlin':
122
123
  while (current) {
123
- if (current.type === 'modifiers' || current.type === 'visibility_modifier') {
124
- const text = current.text || '';
125
- if (text.includes('public') || text.includes('open'))
126
- return true;
124
+ if (current.parent) {
125
+ const visMod = findSiblingChild(current.parent, 'modifiers', 'visibility_modifier');
126
+ if (visMod) {
127
+ const text = visMod.text;
128
+ if (text === 'private' || text === 'internal' || text === 'protected')
129
+ return false;
130
+ if (text === 'public')
131
+ return true;
132
+ }
127
133
  }
128
134
  current = current.parent;
129
135
  }
136
+ // No visibility modifier = public (Kotlin default)
137
+ return true;
138
+ case 'c':
139
+ case 'cpp':
130
140
  return false;
131
141
  case 'php':
132
142
  // Top-level classes/interfaces/traits are always accessible
@@ -145,6 +155,16 @@ const isNodeExported = (node, name, language) => {
145
155
  }
146
156
  // Top-level functions (no parent class) are globally accessible
147
157
  return true;
158
+ case 'swift':
159
+ while (current) {
160
+ if (current.type === 'modifiers' || current.type === 'visibility_modifier') {
161
+ const text = current.text || '';
162
+ if (text.includes('public') || text.includes('open'))
163
+ return true;
164
+ }
165
+ current = current.parent;
166
+ }
167
+ return false;
148
168
  default:
149
169
  return false;
150
170
  }
@@ -158,8 +178,12 @@ const FUNCTION_NODE_TYPES = new Set([
158
178
  'function_definition', 'async_function_declaration', 'async_arrow_function',
159
179
  'method_declaration', 'constructor_declaration',
160
180
  'local_function_statement', 'function_item', 'impl_item',
161
- 'anonymous_function_creation_expression', // PHP anonymous functions
162
- 'init_declaration', 'deinit_declaration', // Swift initializers/deinitializers
181
+ // Kotlin
182
+ 'lambda_literal',
183
+ // PHP
184
+ 'anonymous_function',
185
+ // Swift initializers/deinitializers
186
+ 'init_declaration', 'deinit_declaration',
163
187
  ]);
164
188
  /** Walk up AST to find enclosing function, return its generateId or null for top-level */
165
189
  const findEnclosingFunctionId = (node, filePath) => {
@@ -241,6 +265,22 @@ const BUILT_INS = new Set([
241
265
  'open', 'read', 'write', 'close', 'append', 'extend', 'update',
242
266
  'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
243
267
  'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
268
+ // Kotlin stdlib (IMPORTANT: keep in sync with call-processor.ts BUILT_IN_NAMES)
269
+ 'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error',
270
+ 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
271
+ 'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless',
272
+ 'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet',
273
+ 'repeat', 'synchronized',
274
+ // Kotlin coroutine builders & scope functions
275
+ 'launch', 'async', 'runBlocking', 'withContext', 'coroutineScope',
276
+ 'supervisorScope', 'delay',
277
+ // Kotlin Flow operators
278
+ 'flow', 'flowOf', 'collect', 'emit', 'onEach', 'catch',
279
+ 'buffer', 'conflate', 'distinctUntilChanged',
280
+ 'flatMapLatest', 'flatMapMerge', 'combine',
281
+ 'stateIn', 'shareIn', 'launchIn',
282
+ // Kotlin infix stdlib functions
283
+ 'to', 'until', 'downTo', 'step',
244
284
  // C/C++ standard library
245
285
  'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
246
286
  'scanf', 'fscanf', 'sscanf',
@@ -366,37 +406,49 @@ const getLabelFromCaptures = (captureMap) => {
366
406
  return 'Template';
367
407
  return 'CodeElement';
368
408
  };
409
+ const DEFINITION_CAPTURE_KEYS = [
410
+ 'definition.function',
411
+ 'definition.class',
412
+ 'definition.interface',
413
+ 'definition.method',
414
+ 'definition.struct',
415
+ 'definition.enum',
416
+ 'definition.namespace',
417
+ 'definition.module',
418
+ 'definition.trait',
419
+ 'definition.impl',
420
+ 'definition.type',
421
+ 'definition.const',
422
+ 'definition.static',
423
+ 'definition.typedef',
424
+ 'definition.macro',
425
+ 'definition.union',
426
+ 'definition.property',
427
+ 'definition.record',
428
+ 'definition.delegate',
429
+ 'definition.annotation',
430
+ 'definition.constructor',
431
+ 'definition.template',
432
+ ];
369
433
  const getDefinitionNodeFromCaptures = (captureMap) => {
370
- const definitionKeys = [
371
- 'definition.function',
372
- 'definition.class',
373
- 'definition.interface',
374
- 'definition.method',
375
- 'definition.struct',
376
- 'definition.enum',
377
- 'definition.namespace',
378
- 'definition.module',
379
- 'definition.trait',
380
- 'definition.impl',
381
- 'definition.type',
382
- 'definition.const',
383
- 'definition.static',
384
- 'definition.typedef',
385
- 'definition.macro',
386
- 'definition.union',
387
- 'definition.property',
388
- 'definition.record',
389
- 'definition.delegate',
390
- 'definition.annotation',
391
- 'definition.constructor',
392
- 'definition.template',
393
- ];
394
- for (const key of definitionKeys) {
434
+ for (const key of DEFINITION_CAPTURE_KEYS) {
395
435
  if (captureMap[key])
396
436
  return captureMap[key];
397
437
  }
398
438
  return null;
399
439
  };
440
+ /**
441
+ * Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
442
+ * Pure function — returns a new string without mutating the input.
443
+ */
444
+ const appendKotlinWildcard = (importPath, importNode) => {
445
+ for (let i = 0; i < importNode.childCount; i++) {
446
+ if (importNode.child(i)?.type === 'wildcard_import') {
447
+ return importPath.endsWith('.*') ? importPath : `${importPath}.*`;
448
+ }
449
+ }
450
+ return importPath;
451
+ };
400
452
  // ============================================================================
401
453
  // Process a batch of files
402
454
  // ============================================================================
@@ -408,6 +460,7 @@ const processBatch = (files, onProgress) => {
408
460
  imports: [],
409
461
  calls: [],
410
462
  heritage: [],
463
+ routes: [],
411
464
  fileCount: 0,
412
465
  };
413
466
  // Group by language to minimize setLanguage calls
@@ -455,13 +508,23 @@ const processBatch = (files, onProgress) => {
455
508
  }
456
509
  // Process regular files for this language
457
510
  if (regularFiles.length > 0) {
458
- setLanguage(language, regularFiles[0].path);
459
- processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
511
+ try {
512
+ setLanguage(language, regularFiles[0].path);
513
+ processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
514
+ }
515
+ catch {
516
+ // parser unavailable — skip this language group
517
+ }
460
518
  }
461
519
  // Process tsx files separately (different grammar)
462
520
  if (tsxFiles.length > 0) {
463
- setLanguage(language, tsxFiles[0].path);
464
- processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
521
+ try {
522
+ setLanguage(language, tsxFiles[0].path);
523
+ processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
524
+ }
525
+ catch {
526
+ // parser unavailable — skip this language group
527
+ }
465
528
  }
466
529
  }
467
530
  return result;
@@ -570,6 +633,353 @@ function extractEloquentRelationDescription(methodNode) {
570
633
  return relType;
571
634
  return null;
572
635
  }
636
+ const ROUTE_HTTP_METHODS = new Set([
637
+ 'get', 'post', 'put', 'patch', 'delete', 'options', 'any', 'match',
638
+ ]);
639
+ const ROUTE_RESOURCE_METHODS = new Set(['resource', 'apiResource']);
640
+ const RESOURCE_ACTIONS = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
641
+ const API_RESOURCE_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
642
+ /** Check if node is a scoped_call_expression with object 'Route' */
643
+ function isRouteStaticCall(node) {
644
+ if (node.type !== 'scoped_call_expression')
645
+ return false;
646
+ const obj = node.childForFieldName?.('object') ?? node.children?.[0];
647
+ return obj?.text === 'Route';
648
+ }
649
+ /** Get the method name from a scoped_call_expression or member_call_expression */
650
+ function getCallMethodName(node) {
651
+ const nameNode = node.childForFieldName?.('name') ??
652
+ node.children?.find((c) => c.type === 'name');
653
+ return nameNode?.text ?? null;
654
+ }
655
+ /** Get the arguments node from a call expression */
656
+ function getArguments(node) {
657
+ return node.children?.find((c) => c.type === 'arguments') ?? null;
658
+ }
659
+ /** Find the closure body inside arguments */
660
+ function findClosureBody(argsNode) {
661
+ if (!argsNode)
662
+ return null;
663
+ for (const child of argsNode.children ?? []) {
664
+ if (child.type === 'argument') {
665
+ for (const inner of child.children ?? []) {
666
+ if (inner.type === 'anonymous_function' ||
667
+ inner.type === 'arrow_function') {
668
+ return inner.childForFieldName?.('body') ??
669
+ inner.children?.find((c) => c.type === 'compound_statement');
670
+ }
671
+ }
672
+ }
673
+ if (child.type === 'anonymous_function' ||
674
+ child.type === 'arrow_function') {
675
+ return child.childForFieldName?.('body') ??
676
+ child.children?.find((c) => c.type === 'compound_statement');
677
+ }
678
+ }
679
+ return null;
680
+ }
681
+ /** Extract first string argument from arguments node */
682
+ function extractFirstStringArg(argsNode) {
683
+ if (!argsNode)
684
+ return null;
685
+ for (const child of argsNode.children ?? []) {
686
+ const target = child.type === 'argument' ? child.children?.[0] : child;
687
+ if (!target)
688
+ continue;
689
+ if (target.type === 'string' || target.type === 'encapsed_string') {
690
+ return extractStringContent(target);
691
+ }
692
+ }
693
+ return null;
694
+ }
695
+ /** Extract middleware from arguments — handles string or array */
696
+ function extractMiddlewareArg(argsNode) {
697
+ if (!argsNode)
698
+ return [];
699
+ for (const child of argsNode.children ?? []) {
700
+ const target = child.type === 'argument' ? child.children?.[0] : child;
701
+ if (!target)
702
+ continue;
703
+ if (target.type === 'string' || target.type === 'encapsed_string') {
704
+ const val = extractStringContent(target);
705
+ return val ? [val] : [];
706
+ }
707
+ if (target.type === 'array_creation_expression') {
708
+ const items = [];
709
+ for (const el of target.children ?? []) {
710
+ if (el.type === 'array_element_initializer') {
711
+ const str = el.children?.find((c) => c.type === 'string' || c.type === 'encapsed_string');
712
+ const val = str ? extractStringContent(str) : null;
713
+ if (val)
714
+ items.push(val);
715
+ }
716
+ }
717
+ return items;
718
+ }
719
+ }
720
+ return [];
721
+ }
722
+ /** Extract Controller::class from arguments */
723
+ function extractClassArg(argsNode) {
724
+ if (!argsNode)
725
+ return null;
726
+ for (const child of argsNode.children ?? []) {
727
+ const target = child.type === 'argument' ? child.children?.[0] : child;
728
+ if (target?.type === 'class_constant_access_expression') {
729
+ return target.children?.find((c) => c.type === 'name')?.text ?? null;
730
+ }
731
+ }
732
+ return null;
733
+ }
734
+ /** Extract controller class name from arguments: [Controller::class, 'method'] or 'Controller@method' */
735
+ function extractControllerTarget(argsNode) {
736
+ if (!argsNode)
737
+ return { controller: null, method: null };
738
+ const args = [];
739
+ for (const child of argsNode.children ?? []) {
740
+ if (child.type === 'argument')
741
+ args.push(child.children?.[0]);
742
+ else if (child.type !== '(' && child.type !== ')' && child.type !== ',')
743
+ args.push(child);
744
+ }
745
+ // Second arg is the handler
746
+ const handlerNode = args[1];
747
+ if (!handlerNode)
748
+ return { controller: null, method: null };
749
+ // Array syntax: [UserController::class, 'index']
750
+ if (handlerNode.type === 'array_creation_expression') {
751
+ let controller = null;
752
+ let method = null;
753
+ const elements = [];
754
+ for (const el of handlerNode.children ?? []) {
755
+ if (el.type === 'array_element_initializer')
756
+ elements.push(el);
757
+ }
758
+ if (elements[0]) {
759
+ const classAccess = findDescendant(elements[0], 'class_constant_access_expression');
760
+ if (classAccess) {
761
+ controller = classAccess.children?.find((c) => c.type === 'name')?.text ?? null;
762
+ }
763
+ }
764
+ if (elements[1]) {
765
+ const str = findDescendant(elements[1], 'string');
766
+ method = str ? extractStringContent(str) : null;
767
+ }
768
+ return { controller, method };
769
+ }
770
+ // String syntax: 'UserController@index'
771
+ if (handlerNode.type === 'string' || handlerNode.type === 'encapsed_string') {
772
+ const text = extractStringContent(handlerNode);
773
+ if (text?.includes('@')) {
774
+ const [controller, method] = text.split('@');
775
+ return { controller, method };
776
+ }
777
+ }
778
+ // Class reference: UserController::class (invokable controller)
779
+ if (handlerNode.type === 'class_constant_access_expression') {
780
+ const controller = handlerNode.children?.find((c) => c.type === 'name')?.text ?? null;
781
+ return { controller, method: '__invoke' };
782
+ }
783
+ return { controller: null, method: null };
784
+ }
785
+ /**
786
+ * Unwrap a chained call like Route::middleware('auth')->prefix('api')->group(fn)
787
+ */
788
+ function unwrapRouteChain(node) {
789
+ if (node.type !== 'member_call_expression')
790
+ return null;
791
+ const terminalMethod = getCallMethodName(node);
792
+ if (!terminalMethod)
793
+ return null;
794
+ const terminalArgs = getArguments(node);
795
+ const attributes = [];
796
+ let current = node.children?.[0];
797
+ while (current) {
798
+ if (current.type === 'member_call_expression') {
799
+ const method = getCallMethodName(current);
800
+ const args = getArguments(current);
801
+ if (method)
802
+ attributes.unshift({ method, argsNode: args });
803
+ current = current.children?.[0];
804
+ }
805
+ else if (current.type === 'scoped_call_expression') {
806
+ const obj = current.childForFieldName?.('object') ?? current.children?.[0];
807
+ if (obj?.text !== 'Route')
808
+ return null;
809
+ const method = getCallMethodName(current);
810
+ const args = getArguments(current);
811
+ if (method)
812
+ attributes.unshift({ method, argsNode: args });
813
+ return { isRouteFacade: true, terminalMethod, attributes, terminalArgs, node };
814
+ }
815
+ else {
816
+ break;
817
+ }
818
+ }
819
+ return null;
820
+ }
821
+ /** Parse Route::group(['middleware' => ..., 'prefix' => ...], fn) array syntax */
822
+ function parseArrayGroupArgs(argsNode) {
823
+ const ctx = { middleware: [], prefix: null, controller: null };
824
+ if (!argsNode)
825
+ return ctx;
826
+ for (const child of argsNode.children ?? []) {
827
+ const target = child.type === 'argument' ? child.children?.[0] : child;
828
+ if (target?.type === 'array_creation_expression') {
829
+ for (const el of target.children ?? []) {
830
+ if (el.type !== 'array_element_initializer')
831
+ continue;
832
+ const children = el.children ?? [];
833
+ const arrowIdx = children.findIndex((c) => c.type === '=>');
834
+ if (arrowIdx === -1)
835
+ continue;
836
+ const key = extractStringContent(children[arrowIdx - 1]);
837
+ const val = children[arrowIdx + 1];
838
+ if (key === 'middleware') {
839
+ if (val?.type === 'string') {
840
+ const s = extractStringContent(val);
841
+ if (s)
842
+ ctx.middleware.push(s);
843
+ }
844
+ else if (val?.type === 'array_creation_expression') {
845
+ for (const item of val.children ?? []) {
846
+ if (item.type === 'array_element_initializer') {
847
+ const str = item.children?.find((c) => c.type === 'string');
848
+ const s = str ? extractStringContent(str) : null;
849
+ if (s)
850
+ ctx.middleware.push(s);
851
+ }
852
+ }
853
+ }
854
+ }
855
+ else if (key === 'prefix') {
856
+ ctx.prefix = extractStringContent(val) ?? null;
857
+ }
858
+ else if (key === 'controller') {
859
+ if (val?.type === 'class_constant_access_expression') {
860
+ ctx.controller = val.children?.find((c) => c.type === 'name')?.text ?? null;
861
+ }
862
+ }
863
+ }
864
+ }
865
+ }
866
+ return ctx;
867
+ }
868
+ function extractLaravelRoutes(tree, filePath) {
869
+ const routes = [];
870
+ function resolveStack(stack) {
871
+ const middleware = [];
872
+ let prefix = null;
873
+ let controller = null;
874
+ for (const ctx of stack) {
875
+ middleware.push(...ctx.middleware);
876
+ if (ctx.prefix)
877
+ prefix = prefix ? `${prefix}/${ctx.prefix}`.replace(/\/+/g, '/') : ctx.prefix;
878
+ if (ctx.controller)
879
+ controller = ctx.controller;
880
+ }
881
+ return { middleware, prefix, controller };
882
+ }
883
+ function emitRoute(httpMethod, argsNode, lineNumber, groupStack, chainAttrs) {
884
+ const effective = resolveStack(groupStack);
885
+ for (const attr of chainAttrs) {
886
+ if (attr.method === 'middleware')
887
+ effective.middleware.push(...extractMiddlewareArg(attr.argsNode));
888
+ if (attr.method === 'prefix') {
889
+ const p = extractFirstStringArg(attr.argsNode);
890
+ if (p)
891
+ effective.prefix = effective.prefix ? `${effective.prefix}/${p}` : p;
892
+ }
893
+ if (attr.method === 'controller') {
894
+ const cls = extractClassArg(attr.argsNode);
895
+ if (cls)
896
+ effective.controller = cls;
897
+ }
898
+ }
899
+ const routePath = extractFirstStringArg(argsNode);
900
+ if (ROUTE_RESOURCE_METHODS.has(httpMethod)) {
901
+ const target = extractControllerTarget(argsNode);
902
+ const actions = httpMethod === 'apiResource' ? API_RESOURCE_ACTIONS : RESOURCE_ACTIONS;
903
+ for (const action of actions) {
904
+ routes.push({
905
+ filePath, httpMethod, routePath,
906
+ controllerName: target.controller ?? effective.controller,
907
+ methodName: action,
908
+ middleware: [...effective.middleware],
909
+ prefix: effective.prefix,
910
+ lineNumber,
911
+ });
912
+ }
913
+ }
914
+ else {
915
+ const target = extractControllerTarget(argsNode);
916
+ routes.push({
917
+ filePath, httpMethod, routePath,
918
+ controllerName: target.controller ?? effective.controller,
919
+ methodName: target.method,
920
+ middleware: [...effective.middleware],
921
+ prefix: effective.prefix,
922
+ lineNumber,
923
+ });
924
+ }
925
+ }
926
+ function walk(node, groupStack) {
927
+ // Case 1: Simple Route::get(...), Route::post(...), etc.
928
+ if (isRouteStaticCall(node)) {
929
+ const method = getCallMethodName(node);
930
+ if (method && (ROUTE_HTTP_METHODS.has(method) || ROUTE_RESOURCE_METHODS.has(method))) {
931
+ emitRoute(method, getArguments(node), node.startPosition.row, groupStack, []);
932
+ return;
933
+ }
934
+ if (method === 'group') {
935
+ const argsNode = getArguments(node);
936
+ const groupCtx = parseArrayGroupArgs(argsNode);
937
+ const body = findClosureBody(argsNode);
938
+ if (body) {
939
+ groupStack.push(groupCtx);
940
+ walkChildren(body, groupStack);
941
+ groupStack.pop();
942
+ }
943
+ return;
944
+ }
945
+ }
946
+ // Case 2: Fluent chain — Route::middleware(...)->group(...) or Route::middleware(...)->get(...)
947
+ const chain = unwrapRouteChain(node);
948
+ if (chain) {
949
+ if (chain.terminalMethod === 'group') {
950
+ const groupCtx = { middleware: [], prefix: null, controller: null };
951
+ for (const attr of chain.attributes) {
952
+ if (attr.method === 'middleware')
953
+ groupCtx.middleware.push(...extractMiddlewareArg(attr.argsNode));
954
+ if (attr.method === 'prefix')
955
+ groupCtx.prefix = extractFirstStringArg(attr.argsNode);
956
+ if (attr.method === 'controller')
957
+ groupCtx.controller = extractClassArg(attr.argsNode);
958
+ }
959
+ const body = findClosureBody(chain.terminalArgs);
960
+ if (body) {
961
+ groupStack.push(groupCtx);
962
+ walkChildren(body, groupStack);
963
+ groupStack.pop();
964
+ }
965
+ return;
966
+ }
967
+ if (ROUTE_HTTP_METHODS.has(chain.terminalMethod) || ROUTE_RESOURCE_METHODS.has(chain.terminalMethod)) {
968
+ emitRoute(chain.terminalMethod, chain.terminalArgs, node.startPosition.row, groupStack, chain.attributes);
969
+ return;
970
+ }
971
+ }
972
+ // Default: recurse into children
973
+ walkChildren(node, groupStack);
974
+ }
975
+ function walkChildren(node, groupStack) {
976
+ for (const child of node.children ?? []) {
977
+ walk(child, groupStack);
978
+ }
979
+ }
980
+ walk(tree.rootNode, []);
981
+ return routes;
982
+ }
573
983
  const processFileGroup = (files, language, queryString, result, onFileProcessed) => {
574
984
  let query;
575
985
  try {
@@ -606,7 +1016,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
606
1016
  }
607
1017
  // Extract import paths before skipping
608
1018
  if (captureMap['import'] && captureMap['import.source']) {
609
- const rawImportPath = captureMap['import.source'].text.replace(/['"<>]/g, '');
1019
+ const rawImportPath = language === SupportedLanguages.Kotlin
1020
+ ? appendKotlinWildcard(captureMap['import.source'].text.replace(/['"<>]/g, ''), captureMap['import'])
1021
+ : captureMap['import.source'].text.replace(/['"<>]/g, '');
610
1022
  result.imports.push({
611
1023
  filePath: file.path,
612
1024
  rawImportPath,
@@ -662,8 +1074,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
662
1074
  if (!nodeLabel)
663
1075
  continue;
664
1076
  const nameNode = captureMap['name'];
665
- const nodeName = nameNode.text;
666
- const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
1077
+ // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
1078
+ if (!nameNode && nodeLabel !== 'Constructor')
1079
+ continue;
1080
+ const nodeName = nameNode ? nameNode.text : 'init';
1081
+ const definitionNode = getDefinitionNodeFromCaptures(captureMap);
1082
+ const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
1083
+ const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}:${startLine}`);
667
1084
  let description;
668
1085
  if (language === SupportedLanguages.PHP) {
669
1086
  if (nodeLabel === 'Property' && captureMap['definition.property']) {
@@ -673,9 +1090,8 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
673
1090
  description = extractEloquentRelationDescription(captureMap['definition.method']) ?? undefined;
674
1091
  }
675
1092
  }
676
- const definitionNode = getDefinitionNodeFromCaptures(captureMap);
677
1093
  const frameworkHint = definitionNode
678
- ? detectFrameworkFromAST(language, definitionNode.text || '')
1094
+ ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
679
1095
  : null;
680
1096
  result.nodes.push({
681
1097
  id: nodeId,
@@ -683,10 +1099,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
683
1099
  properties: {
684
1100
  name: nodeName,
685
1101
  filePath: file.path,
686
- startLine: nameNode.startPosition.row,
687
- endLine: nameNode.endPosition.row,
1102
+ startLine: definitionNode ? definitionNode.startPosition.row : startLine,
1103
+ endLine: definitionNode ? definitionNode.endPosition.row : startLine,
688
1104
  language: language,
689
- isExported: isNodeExported(nameNode, nodeName, language),
1105
+ isExported: isNodeExported(nameNode || definitionNode, nodeName, language),
690
1106
  ...(frameworkHint ? {
691
1107
  astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
692
1108
  astFrameworkReason: frameworkHint.reason,
@@ -711,6 +1127,11 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
711
1127
  reason: '',
712
1128
  });
713
1129
  }
1130
+ // Extract Laravel routes from route files via procedural AST walk
1131
+ if (language === SupportedLanguages.PHP && (file.path.includes('/routes/') || file.path.startsWith('routes/')) && file.path.endsWith('.php')) {
1132
+ const extractedRoutes = extractLaravelRoutes(tree, file.path);
1133
+ result.routes.push(...extractedRoutes);
1134
+ }
714
1135
  }
715
1136
  };
716
1137
  // ============================================================================
@@ -719,7 +1140,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
719
1140
  /** Accumulated result across sub-batches */
720
1141
  let accumulated = {
721
1142
  nodes: [], relationships: [], symbols: [],
722
- imports: [], calls: [], heritage: [], fileCount: 0,
1143
+ imports: [], calls: [], heritage: [], routes: [], fileCount: 0,
723
1144
  };
724
1145
  let cumulativeProcessed = 0;
725
1146
  const mergeResult = (target, src) => {
@@ -729,6 +1150,7 @@ const mergeResult = (target, src) => {
729
1150
  target.imports.push(...src.imports);
730
1151
  target.calls.push(...src.calls);
731
1152
  target.heritage.push(...src.heritage);
1153
+ target.routes.push(...src.routes);
732
1154
  target.fileCount += src.fileCount;
733
1155
  };
734
1156
  parentPort.on('message', (msg) => {
@@ -748,7 +1170,7 @@ parentPort.on('message', (msg) => {
748
1170
  if (msg && msg.type === 'flush') {
749
1171
  parentPort.postMessage({ type: 'result', data: accumulated });
750
1172
  // Reset for potential reuse
751
- accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], fileCount: 0 };
1173
+ accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], fileCount: 0 };
752
1174
  cumulativeProcessed = 0;
753
1175
  return;
754
1176
  }
@@ -13,6 +13,10 @@
13
13
  */
14
14
  import { KnowledgeGraph } from '../graph/types.js';
15
15
  import { NodeTableName } from './schema.js';
16
+ export declare const sanitizeUTF8: (str: string) => string;
17
+ export declare const escapeCSVField: (value: string | number | undefined | null) => string;
18
+ export declare const escapeCSVNumber: (value: number | undefined | null, defaultValue?: number) => string;
19
+ export declare const isBinaryContent: (content: string) => boolean;
16
20
  export interface StreamedCSVResult {
17
21
  nodeFiles: Map<NodeTableName, {
18
22
  csvPath: string;