gitnexus 1.3.5 → 1.3.7

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 +0 -5
  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 +6 -21
  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 +481 -52
  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 +12 -2
  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 +14 -4
  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
@@ -141,6 +141,8 @@ const EXTENSIONS = [
141
141
  '.py', '/__init__.py',
142
142
  // Java
143
143
  '.java',
144
+ // Kotlin
145
+ '.kt', '.kts',
144
146
  // C/C++
145
147
  '.c', '.h', '.cpp', '.hpp', '.cc', '.cxx', '.hxx', '.hh',
146
148
  // C#
@@ -406,19 +408,32 @@ function tryRustModulePath(modulePath, allFiles) {
406
408
  }
407
409
  return null;
408
410
  }
411
+ /**
412
+ * Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
413
+ * Pure function — returns a new string without mutating the input.
414
+ */
415
+ const appendKotlinWildcard = (importPath, importNode) => {
416
+ for (let i = 0; i < importNode.childCount; i++) {
417
+ if (importNode.child(i)?.type === 'wildcard_import') {
418
+ return importPath.endsWith('.*') ? importPath : `${importPath}.*`;
419
+ }
420
+ }
421
+ return importPath;
422
+ };
409
423
  // ============================================================================
410
- // JAVA MULTI-FILE RESOLUTION
424
+ // JVM MULTI-FILE RESOLUTION (Java + Kotlin)
411
425
  // ============================================================================
426
+ /** Kotlin file extensions for JVM resolver reuse */
427
+ const KOTLIN_EXTENSIONS = ['.kt', '.kts'];
412
428
  /**
413
- * Resolve a Java wildcard import (com.example.*) to all matching .java files.
414
- * Returns an array of file paths.
429
+ * Resolve a JVM wildcard import (com.example.*) to all matching files.
430
+ * Works for both Java (.java) and Kotlin (.kt, .kts).
415
431
  */
416
- function resolveJavaWildcard(importPath, normalizedFileList, allFileList, index) {
432
+ function resolveJvmWildcard(importPath, normalizedFileList, allFileList, extensions, index) {
417
433
  // "com.example.util.*" -> "com/example/util"
418
434
  const packagePath = importPath.slice(0, -2).replace(/\./g, '/');
419
435
  if (index) {
420
- // Use directory index: get all .java files in this package directory
421
- const candidates = index.getFilesInDir(packagePath, '.java');
436
+ const candidates = extensions.flatMap(ext => index.getFilesInDir(packagePath, ext));
422
437
  // Filter to only direct children (no subdirectories)
423
438
  const packageSuffix = '/' + packagePath + '/';
424
439
  return candidates.filter(f => {
@@ -435,7 +450,8 @@ function resolveJavaWildcard(importPath, normalizedFileList, allFileList, index)
435
450
  const matches = [];
436
451
  for (let i = 0; i < normalizedFileList.length; i++) {
437
452
  const normalized = normalizedFileList[i];
438
- if (normalized.includes(packageSuffix) && normalized.endsWith('.java')) {
453
+ if (normalized.includes(packageSuffix) &&
454
+ extensions.some(ext => normalized.endsWith(ext))) {
439
455
  const afterPackage = normalized.substring(normalized.indexOf(packageSuffix) + packageSuffix.length);
440
456
  if (!afterPackage.includes('/')) {
441
457
  matches.push(allFileList[i]);
@@ -445,29 +461,34 @@ function resolveJavaWildcard(importPath, normalizedFileList, allFileList, index)
445
461
  return matches;
446
462
  }
447
463
  /**
448
- * Try to resolve a Java static import by stripping the member name.
449
- * "com.example.Constants.VALUE" -> resolve "com.example.Constants"
464
+ * Try to resolve a JVM member/static import by stripping the member name.
465
+ * Java: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
466
+ * Kotlin: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
450
467
  */
451
- function resolveJavaStaticImport(importPath, normalizedFileList, allFileList, index) {
452
- // Static imports look like: com.example.Constants.VALUE or com.example.Constants.*
453
- // The last segment is a member name (field/method) if it starts with lowercase or is ALL_CAPS
468
+ function resolveJvmMemberImport(importPath, normalizedFileList, allFileList, extensions, index) {
469
+ // Member imports: com.example.Constants.VALUE or com.example.Constants.*
470
+ // The last segment is a member name if it starts with lowercase, is ALL_CAPS, or is a wildcard
454
471
  const segments = importPath.split('.');
455
472
  if (segments.length < 3)
456
473
  return null;
457
474
  const lastSeg = segments[segments.length - 1];
458
- // If last segment is a wildcard or ALL_CAPS constant or starts with lowercase, strip it
459
475
  if (lastSeg === '*' || /^[a-z]/.test(lastSeg) || /^[A-Z_]+$/.test(lastSeg)) {
460
476
  const classPath = segments.slice(0, -1).join('/');
461
- const classSuffix = classPath + '.java';
462
- if (index) {
463
- return index.get(classSuffix) || index.getInsensitive(classSuffix) || null;
464
- }
465
- // Fallback: linear scan
466
- const fullSuffix = '/' + classSuffix;
467
- for (let i = 0; i < normalizedFileList.length; i++) {
468
- if (normalizedFileList[i].endsWith(fullSuffix) ||
469
- normalizedFileList[i].toLowerCase().endsWith(fullSuffix.toLowerCase())) {
470
- return allFileList[i];
477
+ for (const ext of extensions) {
478
+ const classSuffix = classPath + ext;
479
+ if (index) {
480
+ const result = index.get(classSuffix) || index.getInsensitive(classSuffix);
481
+ if (result)
482
+ return result;
483
+ }
484
+ else {
485
+ const fullSuffix = '/' + classSuffix;
486
+ for (let i = 0; i < normalizedFileList.length; i++) {
487
+ if (normalizedFileList[i].endsWith(fullSuffix) ||
488
+ normalizedFileList[i].toLowerCase().endsWith(fullSuffix.toLowerCase())) {
489
+ return allFileList[i];
490
+ }
491
+ }
471
492
  }
472
493
  }
473
494
  }
@@ -637,24 +658,40 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
637
658
  return;
638
659
  }
639
660
  // Clean path (remove quotes and angle brackets for C/C++ includes)
640
- const rawImportPath = sourceNode.text.replace(/['"<>]/g, '');
661
+ const rawImportPath = language === SupportedLanguages.Kotlin
662
+ ? appendKotlinWildcard(sourceNode.text.replace(/['"<>]/g, ''), captureMap['import'])
663
+ : sourceNode.text.replace(/['"<>]/g, '');
641
664
  totalImportsFound++;
642
- // ---- Java: handle wildcards and static imports specially ----
643
- if (language === SupportedLanguages.Java) {
665
+ // ---- JVM languages (Java + Kotlin): handle wildcards and member imports ----
666
+ if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
667
+ const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
644
668
  if (rawImportPath.endsWith('.*')) {
645
- const matchedFiles = resolveJavaWildcard(rawImportPath, normalizedFileList, allFileList, index);
669
+ const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
670
+ // Kotlin can import Java files in mixed codebases — try .java as fallback
671
+ if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
672
+ const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
673
+ for (const matchedFile of javaMatches) {
674
+ addImportEdge(file.path, matchedFile);
675
+ }
676
+ if (javaMatches.length > 0)
677
+ return;
678
+ }
646
679
  for (const matchedFile of matchedFiles) {
647
680
  addImportEdge(file.path, matchedFile);
648
681
  }
649
682
  return; // skip single-file resolution
650
683
  }
651
- // Try static import resolution (strip member name)
652
- const staticResolved = resolveJavaStaticImport(rawImportPath, normalizedFileList, allFileList, index);
653
- if (staticResolved) {
654
- addImportEdge(file.path, staticResolved);
684
+ // Try member/static import resolution (strip member name)
685
+ let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
686
+ // Kotlin can import Java files in mixed codebases — try .java as fallback
687
+ if (!memberResolved && language === SupportedLanguages.Kotlin) {
688
+ memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
689
+ }
690
+ if (memberResolved) {
691
+ addImportEdge(file.path, memberResolved);
655
692
  return;
656
693
  }
657
- // Fall through to normal resolution for regular Java imports
694
+ // Fall through to normal resolution for regular imports
658
695
  }
659
696
  // ---- Go: handle package-level imports ----
660
697
  if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
@@ -779,19 +816,33 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
779
816
  addImportEdge(filePath, cached);
780
817
  continue;
781
818
  }
782
- // Java: handle wildcards and static imports
783
- if (language === SupportedLanguages.Java) {
819
+ // JVM languages (Java + Kotlin): handle wildcards and member imports
820
+ if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
821
+ const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
784
822
  if (rawImportPath.endsWith('.*')) {
785
- const matchedFiles = resolveJavaWildcard(rawImportPath, normalizedFileList, allFileList, index);
823
+ const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
824
+ // Kotlin can import Java files in mixed codebases — try .java as fallback
825
+ if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
826
+ const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
827
+ for (const matchedFile of javaMatches) {
828
+ addImportEdge(filePath, matchedFile);
829
+ }
830
+ if (javaMatches.length > 0)
831
+ continue;
832
+ }
786
833
  for (const matchedFile of matchedFiles) {
787
834
  addImportEdge(filePath, matchedFile);
788
835
  }
789
836
  continue;
790
837
  }
791
- const staticResolved = resolveJavaStaticImport(rawImportPath, normalizedFileList, allFileList, index);
792
- if (staticResolved) {
793
- resolveCache.set(cacheKey, staticResolved);
794
- addImportEdge(filePath, staticResolved);
838
+ let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
839
+ // Kotlin can import Java files in mixed codebases — try .java as fallback
840
+ if (!memberResolved && language === SupportedLanguages.Kotlin) {
841
+ memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
842
+ }
843
+ if (memberResolved) {
844
+ resolveCache.set(cacheKey, memberResolved);
845
+ addImportEdge(filePath, memberResolved);
795
846
  continue;
796
847
  }
797
848
  }
@@ -2,13 +2,24 @@ import { KnowledgeGraph } from '../graph/types.js';
2
2
  import { SymbolTable } from './symbol-table.js';
3
3
  import { ASTCache } from './ast-cache.js';
4
4
  import { WorkerPool } from './workers/worker-pool.js';
5
- import type { ExtractedImport, ExtractedCall, ExtractedHeritage } from './workers/parse-worker.js';
5
+ import type { ExtractedImport, ExtractedCall, ExtractedHeritage, ExtractedRoute } from './workers/parse-worker.js';
6
6
  export type FileProgressCallback = (current: number, total: number, filePath: string) => void;
7
7
  export interface WorkerExtractedData {
8
8
  imports: ExtractedImport[];
9
9
  calls: ExtractedCall[];
10
10
  heritage: ExtractedHeritage[];
11
+ routes: ExtractedRoute[];
11
12
  }
13
+ /**
14
+ * Check if a symbol (function, class, etc.) is exported/public
15
+ * Handles all 9 supported languages with explicit logic
16
+ *
17
+ * @param node - The AST node for the symbol name
18
+ * @param name - The symbol name
19
+ * @param language - The programming language
20
+ * @returns true if the symbol is exported/public
21
+ */
22
+ export declare const isNodeExported: (node: any, name: string, language: string) => boolean;
12
23
  export declare const processParsing: (graph: KnowledgeGraph, files: {
13
24
  path: string;
14
25
  content: string;
@@ -2,34 +2,34 @@ import Parser from 'tree-sitter';
2
2
  import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
3
3
  import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
4
4
  import { generateId } from '../../lib/utils.js';
5
- import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
5
+ import { findSiblingChild, getLanguageFromFilename, yieldToEventLoop } from './utils.js';
6
6
  import { detectFrameworkFromAST } from './framework-detection.js';
7
+ const DEFINITION_CAPTURE_KEYS = [
8
+ 'definition.function',
9
+ 'definition.class',
10
+ 'definition.interface',
11
+ 'definition.method',
12
+ 'definition.struct',
13
+ 'definition.enum',
14
+ 'definition.namespace',
15
+ 'definition.module',
16
+ 'definition.trait',
17
+ 'definition.impl',
18
+ 'definition.type',
19
+ 'definition.const',
20
+ 'definition.static',
21
+ 'definition.typedef',
22
+ 'definition.macro',
23
+ 'definition.union',
24
+ 'definition.property',
25
+ 'definition.record',
26
+ 'definition.delegate',
27
+ 'definition.annotation',
28
+ 'definition.constructor',
29
+ 'definition.template',
30
+ ];
7
31
  const getDefinitionNodeFromCaptures = (captureMap) => {
8
- const definitionKeys = [
9
- 'definition.function',
10
- 'definition.class',
11
- 'definition.interface',
12
- 'definition.method',
13
- 'definition.struct',
14
- 'definition.enum',
15
- 'definition.namespace',
16
- 'definition.module',
17
- 'definition.trait',
18
- 'definition.impl',
19
- 'definition.type',
20
- 'definition.const',
21
- 'definition.static',
22
- 'definition.typedef',
23
- 'definition.macro',
24
- 'definition.union',
25
- 'definition.property',
26
- 'definition.record',
27
- 'definition.delegate',
28
- 'definition.annotation',
29
- 'definition.constructor',
30
- 'definition.template',
31
- ];
32
- for (const key of definitionKeys) {
32
+ for (const key of DEFINITION_CAPTURE_KEYS) {
33
33
  if (captureMap[key])
34
34
  return captureMap[key];
35
35
  }
@@ -47,7 +47,7 @@ const getDefinitionNodeFromCaptures = (captureMap) => {
47
47
  * @param language - The programming language
48
48
  * @returns true if the symbol is exported/public
49
49
  */
50
- const isNodeExported = (node, name, language) => {
50
+ export const isNodeExported = (node, name, language) => {
51
51
  let current = node;
52
52
  switch (language) {
53
53
  // JavaScript/TypeScript: Check for export keyword in ancestors
@@ -121,6 +121,24 @@ const isNodeExported = (node, name, language) => {
121
121
  current = current.parent;
122
122
  }
123
123
  return false;
124
+ // Kotlin: Default visibility is public (unlike Java)
125
+ // visibility_modifier is inside modifiers, a sibling of the name node within the declaration
126
+ case 'kotlin':
127
+ while (current) {
128
+ if (current.parent) {
129
+ const visMod = findSiblingChild(current.parent, 'modifiers', 'visibility_modifier');
130
+ if (visMod) {
131
+ const text = visMod.text;
132
+ if (text === 'private' || text === 'internal' || text === 'protected')
133
+ return false;
134
+ if (text === 'public')
135
+ return true;
136
+ }
137
+ }
138
+ current = current.parent;
139
+ }
140
+ // No visibility modifier = public (Kotlin default)
141
+ return true;
124
142
  // C/C++: No native export concept at language level
125
143
  // Entry points will be detected via name patterns (main, etc.)
126
144
  case 'c':
@@ -137,6 +155,21 @@ const isNodeExported = (node, name, language) => {
137
155
  current = current.parent;
138
156
  }
139
157
  return false;
158
+ // PHP: Check for visibility modifier or top-level scope
159
+ case 'php':
160
+ while (current) {
161
+ if (current.type === 'class_declaration' ||
162
+ current.type === 'interface_declaration' ||
163
+ current.type === 'trait_declaration' ||
164
+ current.type === 'enum_declaration') {
165
+ return true;
166
+ }
167
+ if (current.type === 'visibility_modifier') {
168
+ return current.text === 'public';
169
+ }
170
+ current = current.parent;
171
+ }
172
+ return true; // Top-level functions are globally accessible
140
173
  default:
141
174
  return false;
142
175
  }
@@ -153,7 +186,7 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
153
186
  parseableFiles.push({ path: file.path, content: file.content });
154
187
  }
155
188
  if (parseableFiles.length === 0)
156
- return { imports: [], calls: [], heritage: [] };
189
+ return { imports: [], calls: [], heritage: [], routes: [] };
157
190
  const total = files.length;
158
191
  // Dispatch to worker pool — pool handles splitting into chunks and sub-batching
159
192
  const chunkResults = await workerPool.dispatch(parseableFiles, (filesProcessed) => {
@@ -163,6 +196,7 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
163
196
  const allImports = [];
164
197
  const allCalls = [];
165
198
  const allHeritage = [];
199
+ const allRoutes = [];
166
200
  for (const result of chunkResults) {
167
201
  for (const node of result.nodes) {
168
202
  graph.addNode({
@@ -180,10 +214,11 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
180
214
  allImports.push(...result.imports);
181
215
  allCalls.push(...result.calls);
182
216
  allHeritage.push(...result.heritage);
217
+ allRoutes.push(...result.routes);
183
218
  }
184
219
  // Final progress
185
220
  onFileProgress?.(total, total, 'done');
186
- return { imports: allImports, calls: allCalls, heritage: allHeritage };
221
+ return { imports: allImports, calls: allCalls, heritage: allHeritage, routes: allRoutes };
187
222
  };
188
223
  // ============================================================================
189
224
  // Sequential fallback (original implementation)
@@ -202,7 +237,12 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
202
237
  // Skip very large files — they can crash tree-sitter or cause OOM
203
238
  if (file.content.length > 512 * 1024)
204
239
  continue;
205
- await loadLanguage(language, file.path);
240
+ try {
241
+ await loadLanguage(language, file.path);
242
+ }
243
+ catch {
244
+ continue; // parser unavailable — already warned in pipeline
245
+ }
206
246
  let tree;
207
247
  try {
208
248
  tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
@@ -239,9 +279,10 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
239
279
  return;
240
280
  }
241
281
  const nameNode = captureMap['name'];
242
- if (!nameNode)
282
+ // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
283
+ if (!nameNode && !captureMap['definition.constructor'])
243
284
  return;
244
- const nodeName = nameNode.text;
285
+ const nodeName = nameNode ? nameNode.text : 'init';
245
286
  let nodeLabel = 'CodeElement';
246
287
  if (captureMap['definition.function'])
247
288
  nodeLabel = 'Function';
@@ -287,28 +328,28 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
287
328
  nodeLabel = 'Constructor';
288
329
  else if (captureMap['definition.template'])
289
330
  nodeLabel = 'Template';
290
- const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
331
+ const definitionNodeForRange = getDefinitionNodeFromCaptures(captureMap);
332
+ const startLine = definitionNodeForRange ? definitionNodeForRange.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
333
+ const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}:${startLine}`);
334
+ const definitionNode = getDefinitionNodeFromCaptures(captureMap);
335
+ const frameworkHint = definitionNode
336
+ ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
337
+ : null;
291
338
  const node = {
292
339
  id: nodeId,
293
340
  label: nodeLabel,
294
- properties: (() => {
295
- const definitionNode = getDefinitionNodeFromCaptures(captureMap);
296
- const frameworkHint = definitionNode
297
- ? detectFrameworkFromAST(language, definitionNode.text || '')
298
- : null;
299
- return {
300
- name: nodeName,
301
- filePath: file.path,
302
- startLine: nameNode.startPosition.row,
303
- endLine: nameNode.endPosition.row,
304
- language: language,
305
- isExported: isNodeExported(nameNode, nodeName, language),
306
- ...(frameworkHint ? {
307
- astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
308
- astFrameworkReason: frameworkHint.reason,
309
- } : {}),
310
- };
311
- })()
341
+ properties: {
342
+ name: nodeName,
343
+ filePath: file.path,
344
+ startLine: definitionNodeForRange ? definitionNodeForRange.startPosition.row : startLine,
345
+ endLine: definitionNodeForRange ? definitionNodeForRange.endPosition.row : startLine,
346
+ language: language,
347
+ isExported: isNodeExported(nameNode || definitionNodeForRange, nodeName, language),
348
+ ...(frameworkHint ? {
349
+ astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
350
+ astFrameworkReason: frameworkHint.reason,
351
+ } : {}),
352
+ },
312
353
  };
313
354
  graph.addNode(node);
314
355
  symbolTable.add(file.path, nodeName, nodeId, nodeLabel);
@@ -2,7 +2,7 @@ import { createKnowledgeGraph } from '../graph/graph.js';
2
2
  import { processStructure } from './structure-processor.js';
3
3
  import { processParsing } from './parsing-processor.js';
4
4
  import { processImports, processImportsFromExtracted, createImportMap, buildImportResolutionContext } from './import-processor.js';
5
- import { processCalls, processCallsFromExtracted } from './call-processor.js';
5
+ import { processCalls, processCallsFromExtracted, processRoutesFromExtracted } from './call-processor.js';
6
6
  import { processHeritage, processHeritageFromExtracted } from './heritage-processor.js';
7
7
  import { processCommunities } from './community-processor.js';
8
8
  import { processProcesses } from './process-processor.js';
@@ -10,6 +10,7 @@ import { createSymbolTable } from './symbol-table.js';
10
10
  import { createASTCache } from './ast-cache.js';
11
11
  import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
12
12
  import { getLanguageFromFilename } from './utils.js';
13
+ import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
13
14
  import { createWorkerPool } from './workers/worker-pool.js';
14
15
  const isDev = process.env.NODE_ENV === 'development';
15
16
  /** Max bytes of source content to load per parse chunk. Each chunk's source +
@@ -70,7 +71,21 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
70
71
  // ── Phase 3+4: Chunked read + parse ────────────────────────────────
71
72
  // Group parseable files into byte-budget chunks so only ~20MB of source
72
73
  // is in memory at a time. Each chunk is: read → parse → extract → free.
73
- const parseableScanned = scannedFiles.filter(f => getLanguageFromFilename(f.path));
74
+ const parseableScanned = scannedFiles.filter(f => {
75
+ const lang = getLanguageFromFilename(f.path);
76
+ return lang && isLanguageAvailable(lang);
77
+ });
78
+ // Warn about files skipped due to unavailable parsers
79
+ const skippedByLang = new Map();
80
+ for (const f of scannedFiles) {
81
+ const lang = getLanguageFromFilename(f.path);
82
+ if (lang && !isLanguageAvailable(lang)) {
83
+ skippedByLang.set(lang, (skippedByLang.get(lang) || 0) + 1);
84
+ }
85
+ }
86
+ for (const [lang, count] of skippedByLang) {
87
+ console.warn(`Skipping ${count} ${lang} file(s) — ${lang} parser not available (native binding may not have built). Try: npm rebuild tree-sitter-${lang}`);
88
+ }
74
89
  const totalParseable = parseableScanned.length;
75
90
  // Build byte-budget chunks
76
91
  const chunks = [];
@@ -151,6 +166,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
151
166
  if (chunkWorkerData.heritage.length > 0) {
152
167
  await processHeritageFromExtracted(graph, chunkWorkerData.heritage, symbolTable);
153
168
  }
169
+ // Routes — resolve immediately (Laravel route→controller CALLS edges)
170
+ if (chunkWorkerData.routes && chunkWorkerData.routes.length > 0) {
171
+ await processRoutesFromExtracted(graph, chunkWorkerData.routes, symbolTable, importMap);
172
+ }
154
173
  }
155
174
  else {
156
175
  await processImports(graph, chunkFiles, astCache, importMap, undefined, repoPath, allPaths);
@@ -219,7 +219,6 @@ const traceFromEntryPoint = (entryId, callsEdges, config) => {
219
219
  // BFS with path tracking
220
220
  // Each queue item: [currentNodeId, pathSoFar]
221
221
  const queue = [[entryId, [entryId]]];
222
- const visited = new Set();
223
222
  while (queue.length > 0 && traces.length < config.maxBranching * 3) {
224
223
  const [currentId, path] = queue.shift();
225
224
  // Get outgoing calls
@@ -9,5 +9,6 @@ export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_spe
9
9
  export declare const CSHARP_QUERIES = "\n; Types\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(struct_declaration name: (identifier) @name) @definition.struct\n(enum_declaration name: (identifier) @name) @definition.enum\n(record_declaration name: (identifier) @name) @definition.record\n(delegate_declaration name: (identifier) @name) @definition.delegate\n\n; Namespaces\n(namespace_declaration name: (identifier) @name) @definition.namespace\n(namespace_declaration name: (qualified_name) @name) @definition.namespace\n\n; Methods & Properties\n(method_declaration name: (identifier) @name) @definition.method\n(local_function_statement name: (identifier) @name) @definition.function\n(constructor_declaration name: (identifier) @name) @definition.constructor\n(property_declaration name: (identifier) @name) @definition.property\n\n; Using\n(using_directive (qualified_name) @import.source) @import\n(using_directive (identifier) @import.source) @import\n\n; Calls\n(invocation_expression function: (identifier) @call.name) @call\n(invocation_expression function: (member_access_expression name: (identifier) @call.name)) @call\n\n; Heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (simple_base_type (identifier) @heritage.extends))) @heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (simple_base_type (generic_name (identifier) @heritage.extends)))) @heritage\n";
10
10
  export declare const RUST_QUERIES = "\n; Functions & Items\n(function_item name: (identifier) @name) @definition.function\n(struct_item name: (type_identifier) @name) @definition.struct\n(enum_item name: (type_identifier) @name) @definition.enum\n(trait_item name: (type_identifier) @name) @definition.trait\n(impl_item type: (type_identifier) @name) @definition.impl\n(mod_item name: (identifier) @name) @definition.module\n\n; Type aliases, const, static, macros\n(type_item name: (type_identifier) @name) @definition.type\n(const_item name: (identifier) @name) @definition.const\n(static_item name: (identifier) @name) @definition.static\n(macro_definition name: (identifier) @name) @definition.macro\n\n; Use statements\n(use_declaration argument: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (scoped_identifier name: (identifier) @call.name)) @call\n(call_expression function: (generic_function function: (identifier) @call.name)) @call\n\n; Heritage (trait implementation)\n(impl_item trait: (type_identifier) @heritage.trait type: (type_identifier) @heritage.class) @heritage\n(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage\n";
11
11
  export declare const PHP_QUERIES = "\n; \u2500\u2500 Namespace \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(namespace_definition\n name: (namespace_name) @name) @definition.namespace\n\n; \u2500\u2500 Classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @name) @definition.class\n\n; \u2500\u2500 Interfaces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(interface_declaration\n name: (name) @name) @definition.interface\n\n; \u2500\u2500 Traits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(trait_declaration\n name: (name) @name) @definition.trait\n\n; \u2500\u2500 Enums (PHP 8.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(enum_declaration\n name: (name) @name) @definition.enum\n\n; \u2500\u2500 Top-level functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(function_definition\n name: (name) @name) @definition.function\n\n; \u2500\u2500 Methods (including constructors) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(method_declaration\n name: (name) @name) @definition.method\n\n; \u2500\u2500 Class properties (including Eloquent $fillable, $casts, etc.) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(property_declaration\n (property_element\n (variable_name\n (name) @name))) @definition.property\n\n; \u2500\u2500 Imports: use statements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Simple: use App\\Models\\User;\n(namespace_use_declaration\n (namespace_use_clause\n (qualified_name) @import.source)) @import\n\n; \u2500\u2500 Function/method calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Regular function call: foo()\n(function_call_expression\n function: (name) @call.name) @call\n\n; Method call: $obj->method()\n(member_call_expression\n name: (name) @call.name) @call\n\n; Nullsafe method call: $obj?->method()\n(nullsafe_member_call_expression\n name: (name) @call.name) @call\n\n; Static call: Foo::bar() (php_only uses scoped_call_expression)\n(scoped_call_expression\n name: (name) @call.name) @call\n\n; \u2500\u2500 Heritage: extends \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (base_clause\n [(name) (qualified_name)] @heritage.extends)) @heritage\n\n; \u2500\u2500 Heritage: implements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (class_interface_clause\n [(name) (qualified_name)] @heritage.implements)) @heritage.impl\n\n; \u2500\u2500 Heritage: use trait (must capture enclosing class name) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n body: (declaration_list\n (use_declaration\n [(name) (qualified_name)] @heritage.trait))) @heritage\n";
12
+ export declare const KOTLIN_QUERIES = "\n; \u2500\u2500 Interfaces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; tree-sitter-kotlin (fwcd) has no interface_declaration node type.\n; Interfaces are class_declaration nodes with an anonymous \"interface\" keyword child.\n(class_declaration\n \"interface\"\n (type_identifier) @name) @definition.interface\n\n; \u2500\u2500 Classes (regular, data, sealed, enum) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; All have the anonymous \"class\" keyword child. enum class has both\n; \"enum\" and \"class\" children \u2014 the \"class\" child still matches.\n(class_declaration\n \"class\"\n (type_identifier) @name) @definition.class\n\n; \u2500\u2500 Object declarations (Kotlin singletons) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(object_declaration\n (type_identifier) @name) @definition.class\n\n; \u2500\u2500 Companion objects (named only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(companion_object\n (type_identifier) @name) @definition.class\n\n; \u2500\u2500 Functions (top-level, member, extension) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(function_declaration\n (simple_identifier) @name) @definition.function\n\n; \u2500\u2500 Properties \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(property_declaration\n (variable_declaration\n (simple_identifier) @name)) @definition.property\n\n; \u2500\u2500 Enum entries \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(enum_entry\n (simple_identifier) @name) @definition.enum\n\n; \u2500\u2500 Type aliases \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(type_alias\n (type_identifier) @name) @definition.type\n\n; \u2500\u2500 Imports \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(import_header\n (identifier) @import.source) @import\n\n; \u2500\u2500 Function calls (direct) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(call_expression\n (simple_identifier) @call.name) @call\n\n; \u2500\u2500 Method calls (via navigation: obj.method()) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(call_expression\n (navigation_expression\n (navigation_suffix\n (simple_identifier) @call.name))) @call\n\n; \u2500\u2500 Constructor invocations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(constructor_invocation\n (user_type\n (type_identifier) @call.name)) @call\n\n; \u2500\u2500 Infix function calls (e.g., a to b, x until y) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(infix_expression\n (simple_identifier) @call.name) @call\n\n; \u2500\u2500 Heritage: extends / implements via delegation_specifier \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Interface implementation (bare user_type): class Foo : Bar\n(class_declaration\n (type_identifier) @heritage.class\n (delegation_specifier\n (user_type (type_identifier) @heritage.extends))) @heritage\n\n; Class extension (constructor_invocation): class Foo : Bar()\n(class_declaration\n (type_identifier) @heritage.class\n (delegation_specifier\n (constructor_invocation\n (user_type (type_identifier) @heritage.extends)))) @heritage\n";
12
13
  export declare const SWIFT_QUERIES = "\n; Classes\n(class_declaration \"class\" name: (type_identifier) @name) @definition.class\n\n; Structs\n(class_declaration \"struct\" name: (type_identifier) @name) @definition.struct\n\n; Enums\n(class_declaration \"enum\" name: (type_identifier) @name) @definition.enum\n\n; Extensions (mapped to class \u2014 no dedicated label in schema)\n(class_declaration \"extension\" name: (user_type (type_identifier) @name)) @definition.class\n\n; Actors\n(class_declaration \"actor\" name: (type_identifier) @name) @definition.class\n\n; Protocols (mapped to interface)\n(protocol_declaration name: (type_identifier) @name) @definition.interface\n\n; Type aliases\n(typealias_declaration name: (type_identifier) @name) @definition.type\n\n; Functions (top-level and methods)\n(function_declaration name: (simple_identifier) @name) @definition.function\n\n; Protocol method declarations\n(protocol_function_declaration name: (simple_identifier) @name) @definition.method\n\n; Initializers\n(init_declaration) @definition.constructor\n\n; Properties (stored and computed)\n(property_declaration (pattern (simple_identifier) @name)) @definition.property\n\n; Imports\n(import_declaration (identifier (simple_identifier) @import.source)) @import\n\n; Calls - direct function calls\n(call_expression (simple_identifier) @call.name) @call\n\n; Calls - member/navigation calls (obj.method())\n(call_expression (navigation_expression (navigation_suffix (simple_identifier) @call.name))) @call\n\n; Heritage - class/struct/enum inheritance and protocol conformance\n(class_declaration name: (type_identifier) @heritage.class\n (inheritance_specifier inherits_from: (user_type (type_identifier) @heritage.extends))) @heritage\n\n; Heritage - protocol inheritance\n(protocol_declaration name: (type_identifier) @heritage.class\n (inheritance_specifier inherits_from: (user_type (type_identifier) @heritage.extends))) @heritage\n";
13
14
  export declare const LANGUAGE_QUERIES: Record<SupportedLanguages, string>;
@@ -384,6 +384,85 @@ export const PHP_QUERIES = `
384
384
  (use_declaration
385
385
  [(name) (qualified_name)] @heritage.trait))) @heritage
386
386
  `;
387
+ // Kotlin queries - works with tree-sitter-kotlin (fwcd/tree-sitter-kotlin)
388
+ // Based on official tags.scm; functions use simple_identifier, classes use type_identifier
389
+ export const KOTLIN_QUERIES = `
390
+ ; ── Interfaces ─────────────────────────────────────────────────────────────
391
+ ; tree-sitter-kotlin (fwcd) has no interface_declaration node type.
392
+ ; Interfaces are class_declaration nodes with an anonymous "interface" keyword child.
393
+ (class_declaration
394
+ "interface"
395
+ (type_identifier) @name) @definition.interface
396
+
397
+ ; ── Classes (regular, data, sealed, enum) ────────────────────────────────
398
+ ; All have the anonymous "class" keyword child. enum class has both
399
+ ; "enum" and "class" children — the "class" child still matches.
400
+ (class_declaration
401
+ "class"
402
+ (type_identifier) @name) @definition.class
403
+
404
+ ; ── Object declarations (Kotlin singletons) ──────────────────────────────
405
+ (object_declaration
406
+ (type_identifier) @name) @definition.class
407
+
408
+ ; ── Companion objects (named only) ───────────────────────────────────────
409
+ (companion_object
410
+ (type_identifier) @name) @definition.class
411
+
412
+ ; ── Functions (top-level, member, extension) ──────────────────────────────
413
+ (function_declaration
414
+ (simple_identifier) @name) @definition.function
415
+
416
+ ; ── Properties ───────────────────────────────────────────────────────────
417
+ (property_declaration
418
+ (variable_declaration
419
+ (simple_identifier) @name)) @definition.property
420
+
421
+ ; ── Enum entries ─────────────────────────────────────────────────────────
422
+ (enum_entry
423
+ (simple_identifier) @name) @definition.enum
424
+
425
+ ; ── Type aliases ─────────────────────────────────────────────────────────
426
+ (type_alias
427
+ (type_identifier) @name) @definition.type
428
+
429
+ ; ── Imports ──────────────────────────────────────────────────────────────
430
+ (import_header
431
+ (identifier) @import.source) @import
432
+
433
+ ; ── Function calls (direct) ──────────────────────────────────────────────
434
+ (call_expression
435
+ (simple_identifier) @call.name) @call
436
+
437
+ ; ── Method calls (via navigation: obj.method()) ──────────────────────────
438
+ (call_expression
439
+ (navigation_expression
440
+ (navigation_suffix
441
+ (simple_identifier) @call.name))) @call
442
+
443
+ ; ── Constructor invocations ──────────────────────────────────────────────
444
+ (constructor_invocation
445
+ (user_type
446
+ (type_identifier) @call.name)) @call
447
+
448
+ ; ── Infix function calls (e.g., a to b, x until y) ──────────────────────
449
+ (infix_expression
450
+ (simple_identifier) @call.name) @call
451
+
452
+ ; ── Heritage: extends / implements via delegation_specifier ──────────────
453
+ ; Interface implementation (bare user_type): class Foo : Bar
454
+ (class_declaration
455
+ (type_identifier) @heritage.class
456
+ (delegation_specifier
457
+ (user_type (type_identifier) @heritage.extends))) @heritage
458
+
459
+ ; Class extension (constructor_invocation): class Foo : Bar()
460
+ (class_declaration
461
+ (type_identifier) @heritage.class
462
+ (delegation_specifier
463
+ (constructor_invocation
464
+ (user_type (type_identifier) @heritage.extends)))) @heritage
465
+ `;
387
466
  // Swift queries - works with tree-sitter-swift
388
467
  export const SWIFT_QUERIES = `
389
468
  ; Classes
@@ -447,5 +526,6 @@ export const LANGUAGE_QUERIES = {
447
526
  [SupportedLanguages.CSharp]: CSHARP_QUERIES,
448
527
  [SupportedLanguages.Rust]: RUST_QUERIES,
449
528
  [SupportedLanguages.PHP]: PHP_QUERIES,
529
+ [SupportedLanguages.Kotlin]: KOTLIN_QUERIES,
450
530
  [SupportedLanguages.Swift]: SWIFT_QUERIES,
451
531
  };