@veewo/gitnexus 1.5.2 → 1.5.4

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.
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Clean Command
3
3
  *
4
- * Removes the .gitnexus index from the current repository.
5
- * Also unregisters it from the global registry.
4
+ * Removes the GitNexus index from the current repository while preserving
5
+ * configuration files (e.g. sync-manifest.txt). Also unregisters the repo
6
+ * from the global registry.
6
7
  */
7
8
  export declare const cleanCommand: (options?: {
8
9
  force?: boolean;
package/dist/cli/clean.js CHANGED
@@ -1,11 +1,32 @@
1
1
  /**
2
2
  * Clean Command
3
3
  *
4
- * Removes the .gitnexus index from the current repository.
5
- * Also unregisters it from the global registry.
4
+ * Removes the GitNexus index from the current repository while preserving
5
+ * configuration files (e.g. sync-manifest.txt). Also unregisters the repo
6
+ * from the global registry.
6
7
  */
7
8
  import fs from 'fs/promises';
9
+ import path from 'path';
8
10
  import { findRepo, unregisterRepo, listRegisteredRepos } from '../storage/repo-manager.js';
11
+ /** Files under .gitnexus/ that are configuration, not index data. */
12
+ const PRESERVE_FILES = new Set(['sync-manifest.txt']);
13
+ async function cleanStoragePath(storagePath) {
14
+ let entries;
15
+ try {
16
+ entries = await fs.readdir(storagePath);
17
+ }
18
+ catch (err) {
19
+ if (err.code === 'ENOENT')
20
+ return;
21
+ throw err;
22
+ }
23
+ for (const entry of entries) {
24
+ if (PRESERVE_FILES.has(entry))
25
+ continue;
26
+ const fullPath = path.join(storagePath, entry);
27
+ await fs.rm(fullPath, { recursive: true, force: true });
28
+ }
29
+ }
9
30
  export const cleanCommand = async (options) => {
10
31
  // --all flag: clean all indexed repos
11
32
  if (options?.all) {
@@ -15,22 +36,22 @@ export const cleanCommand = async (options) => {
15
36
  console.log('No indexed repositories found.');
16
37
  return;
17
38
  }
18
- console.log(`This will delete GitNexus indexes for ${entries.length} repo(s):`);
39
+ console.log(`This will clean GitNexus indexes for ${entries.length} repo(s):`);
19
40
  for (const entry of entries) {
20
41
  console.log(` - ${entry.name} (${entry.path})`);
21
42
  }
22
- console.log('\nRun with --force to confirm deletion.');
43
+ console.log('\nRun with --force to confirm.');
23
44
  return;
24
45
  }
25
46
  const entries = await listRegisteredRepos();
26
47
  for (const entry of entries) {
27
48
  try {
28
- await fs.rm(entry.storagePath, { recursive: true, force: true });
49
+ await cleanStoragePath(entry.storagePath);
29
50
  await unregisterRepo(entry.path);
30
- console.log(`Deleted: ${entry.name} (${entry.storagePath})`);
51
+ console.log(`Cleaned: ${entry.name} (${entry.storagePath})`);
31
52
  }
32
53
  catch (err) {
33
- console.error(`Failed to delete ${entry.name}:`, err);
54
+ console.error(`Failed to clean ${entry.name}:`, err);
34
55
  }
35
56
  }
36
57
  return;
@@ -44,17 +65,17 @@ export const cleanCommand = async (options) => {
44
65
  }
45
66
  const repoName = repo.repoPath.split(/[/\\]/).pop() || repo.repoPath;
46
67
  if (!options?.force) {
47
- console.log(`This will delete the GitNexus index for: ${repoName}`);
68
+ console.log(`This will clean the GitNexus index for: ${repoName}`);
48
69
  console.log(` Path: ${repo.storagePath}`);
49
- console.log('\nRun with --force to confirm deletion.');
70
+ console.log('\nRun with --force to confirm.');
50
71
  return;
51
72
  }
52
73
  try {
53
- await fs.rm(repo.storagePath, { recursive: true, force: true });
74
+ await cleanStoragePath(repo.storagePath);
54
75
  await unregisterRepo(repo.repoPath);
55
- console.log(`Deleted: ${repo.storagePath}`);
76
+ console.log(`Cleaned: ${repo.storagePath}`);
56
77
  }
57
78
  catch (err) {
58
- console.error('Failed to delete:', err);
79
+ console.error('Failed to clean:', err);
59
80
  }
60
81
  };
@@ -11,5 +11,6 @@ export declare enum SupportedLanguages {
11
11
  Rust = "rust",
12
12
  PHP = "php",
13
13
  Kotlin = "kotlin",
14
- Swift = "swift"
14
+ Swift = "swift",
15
+ GDScript = "gdscript"
15
16
  }
@@ -13,4 +13,5 @@ export var SupportedLanguages;
13
13
  SupportedLanguages["PHP"] = "php";
14
14
  SupportedLanguages["Kotlin"] = "kotlin";
15
15
  SupportedLanguages["Swift"] = "swift";
16
+ SupportedLanguages["GDScript"] = "gdscript";
16
17
  })(SupportedLanguages || (SupportedLanguages = {}));
@@ -28,6 +28,7 @@ export const callRouters = {
28
28
  [SupportedLanguages.CPlusPlus]: noRouting,
29
29
  [SupportedLanguages.C]: noRouting,
30
30
  [SupportedLanguages.Ruby]: routeRubyCall,
31
+ [SupportedLanguages.GDScript]: noRouting,
31
32
  };
32
33
  // ── Pre-allocated singletons for common return values ────────────────────────
33
34
  const CALL_RESULT = { kind: 'call' };
@@ -212,6 +212,7 @@ const exportCheckers = {
212
212
  [SupportedLanguages.PHP]: phpExportChecker,
213
213
  [SupportedLanguages.Swift]: swiftExportChecker,
214
214
  [SupportedLanguages.Ruby]: (_node, _name) => true,
215
+ [SupportedLanguages.GDScript]: (_node, name) => !name.startsWith('_'),
215
216
  };
216
217
  // ============================================================================
217
218
  // Public API
@@ -104,8 +104,10 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
104
104
  skippedLanguages.set(language, (skippedLanguages.get(language) || 0) + 1);
105
105
  continue;
106
106
  }
107
- // Skip files larger than the max tree-sitter buffer (32 MB)
108
- if (file.content.length > TREE_SITTER_MAX_BUFFER)
107
+ // Skip files larger than the max tree-sitter buffer (32 MB).
108
+ // Use UTF-8 bytes, not JS string length, because tree-sitter buffer sizing
109
+ // is byte-oriented and multi-byte source can otherwise slip past the cap.
110
+ if (Buffer.byteLength(file.content, 'utf8') > TREE_SITTER_MAX_BUFFER)
109
111
  continue;
110
112
  try {
111
113
  await loadLanguage(language, file.path);
@@ -11,5 +11,6 @@ export declare const RUST_QUERIES = "\n; Functions & Items\n(function_item name:
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; Constructor call: new User()\n(object_creation_expression (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
12
  export declare const RUBY_QUERIES = "\n; \u2500\u2500 Modules \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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(module\n name: (constant) @name) @definition.module\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\n name: (constant) @name) @definition.class\n\n; \u2500\u2500 Instance methods \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\n name: (identifier) @name) @definition.method\n\n; \u2500\u2500 Singleton (class-level) methods \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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(singleton_method\n name: (identifier) @name) @definition.method\n\n; \u2500\u2500 All calls (require, include, attr_*, and regular calls routed in JS) \u2500\u2500\u2500\u2500\u2500\n(call\n method: (identifier) @call.name) @call\n\n; \u2500\u2500 Bare calls without parens (identifiers at statement level are method calls) \u2500\n; NOTE: This may over-capture variable reads as calls (e.g. 'result' at\n; statement level). Ruby's grammar makes bare identifiers ambiguous \u2014 they\n; could be local variables or zero-arity method calls. Post-processing via\n; isBuiltInOrNoise and symbol resolution filtering suppresses most false\n; positives, but a variable name that coincidentally matches a method name\n; elsewhere may produce a false CALLS edge.\n(body_statement\n (identifier) @call.name @call)\n\n; \u2500\u2500 Heritage: class < SuperClass \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\n name: (constant) @heritage.class\n superclass: (superclass\n (constant) @heritage.extends)) @heritage\n";
13
13
  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";
14
+ export declare const GDSCRIPT_QUERIES = "\n; \u2500\u2500 Class Name (file-level class) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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_name_statement\n (name) @name) @definition.class\n\n; \u2500\u2500 Inner Class Definition \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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_definition\n name: (name) @name) @definition.class\n\n; \u2500\u2500 Functions & Methods \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 Constructor (_init) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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_definition) @definition.constructor\n\n; \u2500\u2500 Constructor with name capture for symbol extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(constructor_definition\n parameters: (parameters)) @definition.constructor\n\n; \u2500\u2500 Signals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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(signal_statement\n name: (name) @name) @definition.signal\n\n; \u2500\u2500 Enums \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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_definition\n name: (name) @name) @definition.enum\n\n; \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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(const_statement\n (name) @name) @definition.const\n\n; \u2500\u2500 Extends (inheritance) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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(extends_statement\n (type (identifier) @heritage.extends)) @heritage\n\n; \u2500\u2500 Class with 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\n(class_definition\n name: (name) @heritage.class\n extends: (extends_statement\n (type (identifier) @heritage.extends))) @heritage\n\n; \u2500\u2500 Preload/Load (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\n; Only preload(\"res://...\") and load(\"res://...\") are real imports.\n; Generic call expressions must NOT be tagged @import to avoid double-classifying\n; call sites as imports (which corrupts import resolution and call routing).\n(call\n function: (identifier) @_preload\n arguments: (arguments (string) @import.source)) @import\n(#eq? @_preload \"preload\")\n\n(call\n function: (identifier) @_load\n arguments: (arguments (string) @import.source)) @import\n(#eq? @_load \"load\")\n\n; \u2500\u2500 Function 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\u2500\u2500\u2500\u2500\u2500\u2500\n(call\n function: (identifier) @call.name) @call\n\n";
14
15
  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\n; Heritage - extension protocol conformance (e.g. extension Foo: SomeProtocol)\n; Extensions wrap the name in user_type unlike class/struct/enum declarations\n(class_declaration \"extension\" name: (user_type (type_identifier) @heritage.class)\n (inheritance_specifier inherits_from: (user_type (type_identifier) @heritage.extends))) @heritage\n";
15
16
  export declare const LANGUAGE_QUERIES: Record<SupportedLanguages, string>;
@@ -615,6 +615,67 @@ export const KOTLIN_QUERIES = `
615
615
  (user_type (type_identifier) @heritage.extends)))) @heritage
616
616
  `;
617
617
  // Swift queries - works with tree-sitter-swift
618
+ export const GDSCRIPT_QUERIES = `
619
+ ; ── Class Name (file-level class) ───────────────────────────────────────────
620
+ (class_name_statement
621
+ (name) @name) @definition.class
622
+
623
+ ; ── Inner Class Definition ──────────────────────────────────────────────────
624
+ (class_definition
625
+ name: (name) @name) @definition.class
626
+
627
+ ; ── Functions & Methods ─────────────────────────────────────────────────────
628
+ (function_definition
629
+ name: (name) @name) @definition.function
630
+
631
+ ; ── Constructor (_init) ─────────────────────────────────────────────────────
632
+ (constructor_definition) @definition.constructor
633
+
634
+ ; ── Constructor with name capture for symbol extraction ────────────────────
635
+ (constructor_definition
636
+ parameters: (parameters)) @definition.constructor
637
+
638
+ ; ── Signals ─────────────────────────────────────────────────────────────────
639
+ (signal_statement
640
+ name: (name) @name) @definition.signal
641
+
642
+ ; ── Enums ───────────────────────────────────────────────────────────────────
643
+ (enum_definition
644
+ name: (name) @name) @definition.enum
645
+
646
+ ; ── Constants ───────────────────────────────────────────────────────────────
647
+ (const_statement
648
+ (name) @name) @definition.const
649
+
650
+ ; ── Extends (inheritance) ───────────────────────────────────────────────────
651
+ (extends_statement
652
+ (type (identifier) @heritage.extends)) @heritage
653
+
654
+ ; ── Class with extends ──────────────────────────────────────────────────────
655
+ (class_definition
656
+ name: (name) @heritage.class
657
+ extends: (extends_statement
658
+ (type (identifier) @heritage.extends))) @heritage
659
+
660
+ ; ── Preload/Load (imports) ──────────────────────────────────────────────────
661
+ ; Only preload("res://...") and load("res://...") are real imports.
662
+ ; Generic call expressions must NOT be tagged @import to avoid double-classifying
663
+ ; call sites as imports (which corrupts import resolution and call routing).
664
+ (call
665
+ function: (identifier) @_preload
666
+ arguments: (arguments (string) @import.source)) @import
667
+ (#eq? @_preload "preload")
668
+
669
+ (call
670
+ function: (identifier) @_load
671
+ arguments: (arguments (string) @import.source)) @import
672
+ (#eq? @_load "load")
673
+
674
+ ; ── Function Calls ──────────────────────────────────────────────────────────
675
+ (call
676
+ function: (identifier) @call.name) @call
677
+
678
+ `;
618
679
  export const SWIFT_QUERIES = `
619
680
  ; Classes
620
681
  (class_declaration "class" name: (type_identifier) @name) @definition.class
@@ -685,4 +746,5 @@ export const LANGUAGE_QUERIES = {
685
746
  [SupportedLanguages.PHP]: PHP_QUERIES,
686
747
  [SupportedLanguages.Kotlin]: KOTLIN_QUERIES,
687
748
  [SupportedLanguages.Swift]: SWIFT_QUERIES,
749
+ [SupportedLanguages.GDScript]: GDSCRIPT_QUERIES,
688
750
  };
@@ -17,6 +17,7 @@ export declare const typeConfigs: {
17
17
  cpp: LanguageTypeConfig;
18
18
  php: LanguageTypeConfig;
19
19
  ruby: LanguageTypeConfig;
20
+ gdscript: any;
20
21
  };
21
22
  export type { LanguageTypeConfig, TypeBindingExtractor, ParameterExtractor, ConstructorBindingScanner, ForLoopExtractor, PendingAssignmentExtractor, PatternBindingExtractor, } from './types.js';
22
23
  export { TYPED_PARAMETER_TYPES, extractSimpleTypeName, extractGenericTypeArgs, extractVarName, findChildByType, extractRubyConstructorAssignment } from './shared.js';
@@ -27,5 +27,13 @@ export const typeConfigs = {
27
27
  [SupportedLanguages.CPlusPlus]: cCppConfig,
28
28
  [SupportedLanguages.PHP]: phpConfig,
29
29
  [SupportedLanguages.Ruby]: rubyConfig,
30
+ [SupportedLanguages.GDScript]: {
31
+ declarationNodeTypes: new Set(),
32
+ extractDeclaration: () => null,
33
+ extractParameter: () => null,
34
+ extractConstructor: () => null,
35
+ extractReturnBinding: () => null,
36
+ extractPatternBinding: () => null,
37
+ },
30
38
  };
31
39
  export { TYPED_PARAMETER_TYPES, extractSimpleTypeName, extractGenericTypeArgs, extractVarName, findChildByType, extractRubyConstructorAssignment } from './shared.js';
@@ -612,6 +612,9 @@ export const getLanguageFromFilename = (filename) => {
612
612
  // Swift (extensions)
613
613
  if (filename.endsWith('.swift'))
614
614
  return SupportedLanguages.Swift;
615
+ // GDScript (Godot Engine)
616
+ if (filename.endsWith('.gd'))
617
+ return SupportedLanguages.GDScript;
615
618
  return null;
616
619
  };
617
620
  const CALL_ARGUMENT_LIST_TYPES = new Set([
@@ -14,20 +14,8 @@ import Ruby from 'tree-sitter-ruby';
14
14
  import { createRequire } from 'node:module';
15
15
  import { SupportedLanguages } from '../../../config/supported-languages.js';
16
16
  import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
17
- import { TREE_SITTER_MAX_BUFFER } from '../constants.js';
18
- // tree-sitter-swift is an optionalDependency — may not be installed
17
+ import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
19
18
  const _require = createRequire(import.meta.url);
20
- let Swift = null;
21
- try {
22
- Swift = _require('tree-sitter-swift');
23
- }
24
- catch { }
25
- // tree-sitter-kotlin is an optionalDependency — may not be installed
26
- let Kotlin = null;
27
- try {
28
- Kotlin = _require('tree-sitter-kotlin');
29
- }
30
- catch { }
31
19
  import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, CALL_EXPRESSION_TYPES, extractCallChain, } from '../utils.js';
32
20
  import { buildTypeEnv } from '../type-env.js';
33
21
  import { isNodeExported } from '../export-detection.js';
@@ -41,7 +29,7 @@ import { callRouters } from '../call-routing.js';
41
29
  // Worker-local parser + language map
42
30
  // ============================================================================
43
31
  const parser = new Parser();
44
- const languageMap = {
32
+ const requiredLanguageMap = {
45
33
  [SupportedLanguages.JavaScript]: JavaScript,
46
34
  [SupportedLanguages.TypeScript]: TypeScript.typescript,
47
35
  [`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
@@ -52,10 +40,60 @@ const languageMap = {
52
40
  [SupportedLanguages.CSharp]: CSharp,
53
41
  [SupportedLanguages.Go]: Go,
54
42
  [SupportedLanguages.Rust]: Rust,
55
- ...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
56
43
  [SupportedLanguages.PHP]: PHP.php_only,
57
44
  [SupportedLanguages.Ruby]: Ruby,
58
- ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
45
+ };
46
+ const optionalLanguagePackages = {
47
+ [SupportedLanguages.GDScript]: 'tree-sitter-gdscript',
48
+ [SupportedLanguages.Swift]: 'tree-sitter-swift',
49
+ [SupportedLanguages.Kotlin]: 'tree-sitter-kotlin',
50
+ };
51
+ const optionalLanguageCache = new Map();
52
+ const optionalAvailabilityCache = new Map();
53
+ const isOptionalLanguageInstalled = (language) => {
54
+ if (optionalAvailabilityCache.has(language)) {
55
+ return optionalAvailabilityCache.get(language);
56
+ }
57
+ const packageName = optionalLanguagePackages[language];
58
+ if (!packageName) {
59
+ optionalAvailabilityCache.set(language, false);
60
+ return false;
61
+ }
62
+ try {
63
+ _require.resolve(packageName);
64
+ optionalAvailabilityCache.set(language, true);
65
+ return true;
66
+ }
67
+ catch {
68
+ optionalAvailabilityCache.set(language, false);
69
+ return false;
70
+ }
71
+ };
72
+ const loadOptionalLanguage = (language) => {
73
+ if (optionalLanguageCache.has(language)) {
74
+ return optionalLanguageCache.get(language);
75
+ }
76
+ const packageName = optionalLanguagePackages[language];
77
+ if (!packageName) {
78
+ optionalLanguageCache.set(language, null);
79
+ return null;
80
+ }
81
+ try {
82
+ const grammar = _require(packageName);
83
+ optionalLanguageCache.set(language, grammar);
84
+ return grammar;
85
+ }
86
+ catch {
87
+ optionalLanguageCache.set(language, null);
88
+ optionalAvailabilityCache.set(language, false);
89
+ return null;
90
+ }
91
+ };
92
+ const resolveLanguage = (key, language) => {
93
+ if (key in requiredLanguageMap) {
94
+ return requiredLanguageMap[key];
95
+ }
96
+ return loadOptionalLanguage(language);
59
97
  };
60
98
  /**
61
99
  * Check if a language grammar is available in this worker.
@@ -67,13 +105,13 @@ const isLanguageAvailable = (language, filePath) => {
67
105
  const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
68
106
  ? `${language}:tsx`
69
107
  : language;
70
- return key in languageMap && languageMap[key] != null;
108
+ return key in requiredLanguageMap || isOptionalLanguageInstalled(language);
71
109
  };
72
110
  const setLanguage = (language, filePath) => {
73
111
  const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
74
112
  ? `${language}:tsx`
75
113
  : language;
76
- const lang = languageMap[key];
114
+ const lang = resolveLanguage(key, language);
77
115
  if (!lang)
78
116
  throw new Error(`Unsupported language: ${language}`);
79
117
  parser.setLanguage(lang);
@@ -713,27 +751,23 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
713
751
  return;
714
752
  }
715
753
  for (const file of files) {
716
- // Skip files larger than the max tree-sitter buffer (32 MB)
717
- if (file.content.length > TREE_SITTER_MAX_BUFFER)
754
+ // Skip files larger than the max tree-sitter buffer (32 MB).
755
+ // tree-sitter buffer sizing is byte-oriented; JS string length undercounts
756
+ // UTF-8 multi-byte source and can route oversized input into native code.
757
+ if (Buffer.byteLength(file.content, 'utf8') > TREE_SITTER_MAX_BUFFER)
718
758
  continue;
719
759
  let tree;
720
760
  let usedRawContentFallback = false;
721
761
  try {
722
- const MAX_CHUNK = 4096;
723
- tree = parser.parse((index) => {
724
- if (index >= file.content.length)
725
- return null;
726
- return file.content.slice(index, index + MAX_CHUNK);
762
+ tree = parser.parse(file.content, null, {
763
+ bufferSize: getTreeSitterBufferSize(Buffer.byteLength(file.content, 'utf8')),
727
764
  });
728
765
  }
729
766
  catch (err) {
730
767
  if (file.rawContent && file.rawContent !== file.content) {
731
768
  try {
732
- const MAX_CHUNK = 4096;
733
- tree = parser.parse((index) => {
734
- if (index >= file.rawContent.length)
735
- return null;
736
- return file.rawContent.slice(index, index + MAX_CHUNK);
769
+ tree = parser.parse(file.rawContent, null, {
770
+ bufferSize: getTreeSitterBufferSize(Buffer.byteLength(file.rawContent, 'utf8')),
737
771
  });
738
772
  usedRawContentFallback = true;
739
773
  }
@@ -749,11 +783,8 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
749
783
  }
750
784
  if (file.rawContent && file.rawContent !== file.content && tree.rootNode?.hasError) {
751
785
  try {
752
- const MAX_CHUNK = 4096;
753
- const rawTree = parser.parse((index) => {
754
- if (index >= file.rawContent.length)
755
- return null;
756
- return file.rawContent.slice(index, index + MAX_CHUNK);
786
+ const rawTree = parser.parse(file.rawContent, null, {
787
+ bufferSize: getTreeSitterBufferSize(Buffer.byteLength(file.rawContent, 'utf8')),
757
788
  });
758
789
  if (!rawTree.rootNode?.hasError) {
759
790
  tree = rawTree;
@@ -4,9 +4,13 @@ export declare const isLanguageAvailable: (language: SupportedLanguages) => bool
4
4
  export declare const loadParser: () => Promise<Parser>;
5
5
  export declare const loadLanguage: (language: SupportedLanguages, filePath?: string) => Promise<void>;
6
6
  /**
7
- * Parse source code using tree-sitter's chunked callback API.
8
- * Avoids the native binding's single-buffer size limit (< 32768 bytes)
9
- * that causes "Invalid argument" errors on large files.
7
+ * Parse source code using tree-sitter's string input path with an adaptive
8
+ * native buffer size.
9
+ *
10
+ * The callback input API receives byte offsets. Returning JavaScript string
11
+ * slices from those byte offsets is unsafe for UTF-8/multi-byte content and has
12
+ * caused native tree-sitter crashes in large repositories. Use the stable string
13
+ * input path instead and raise tree-sitter's internal buffer for large files.
10
14
  *
11
15
  * @param content - Full source file content as UTF-8 string
12
16
  * @param oldTree - Optional previous tree for incremental parsing (must call tree.edit() first)
@@ -12,21 +12,10 @@ import PHP from 'tree-sitter-php';
12
12
  import Ruby from 'tree-sitter-ruby';
13
13
  import { createRequire } from 'node:module';
14
14
  import { SupportedLanguages } from '../../config/supported-languages.js';
15
- // tree-sitter-swift is an optionalDependency — may not be installed
15
+ import { getTreeSitterBufferSize } from '../ingestion/constants.js';
16
16
  const _require = createRequire(import.meta.url);
17
- let Swift = null;
18
- try {
19
- Swift = _require('tree-sitter-swift');
20
- }
21
- catch { }
22
- // tree-sitter-kotlin is an optionalDependency — may not be installed
23
- let Kotlin = null;
24
- try {
25
- Kotlin = _require('tree-sitter-kotlin');
26
- }
27
- catch { }
28
17
  let parser = null;
29
- const languageMap = {
18
+ const requiredLanguageMap = {
30
19
  [SupportedLanguages.JavaScript]: JavaScript,
31
20
  [SupportedLanguages.TypeScript]: TypeScript.typescript,
32
21
  [`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
@@ -37,12 +26,62 @@ const languageMap = {
37
26
  [SupportedLanguages.CSharp]: CSharp,
38
27
  [SupportedLanguages.Go]: Go,
39
28
  [SupportedLanguages.Rust]: Rust,
40
- ...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
41
29
  [SupportedLanguages.PHP]: PHP.php_only,
42
30
  [SupportedLanguages.Ruby]: Ruby,
43
- ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
44
31
  };
45
- export const isLanguageAvailable = (language) => language in languageMap;
32
+ const optionalLanguagePackages = {
33
+ [SupportedLanguages.GDScript]: 'tree-sitter-gdscript',
34
+ [SupportedLanguages.Swift]: 'tree-sitter-swift',
35
+ [SupportedLanguages.Kotlin]: 'tree-sitter-kotlin',
36
+ };
37
+ const optionalLanguageCache = new Map();
38
+ const optionalAvailabilityCache = new Map();
39
+ const isOptionalLanguageInstalled = (language) => {
40
+ if (optionalAvailabilityCache.has(language)) {
41
+ return optionalAvailabilityCache.get(language);
42
+ }
43
+ const packageName = optionalLanguagePackages[language];
44
+ if (!packageName) {
45
+ optionalAvailabilityCache.set(language, false);
46
+ return false;
47
+ }
48
+ try {
49
+ _require.resolve(packageName);
50
+ optionalAvailabilityCache.set(language, true);
51
+ return true;
52
+ }
53
+ catch {
54
+ optionalAvailabilityCache.set(language, false);
55
+ return false;
56
+ }
57
+ };
58
+ const loadOptionalLanguage = (language) => {
59
+ if (optionalLanguageCache.has(language)) {
60
+ return optionalLanguageCache.get(language);
61
+ }
62
+ const packageName = optionalLanguagePackages[language];
63
+ if (!packageName) {
64
+ optionalLanguageCache.set(language, null);
65
+ return null;
66
+ }
67
+ try {
68
+ const grammar = _require(packageName);
69
+ optionalLanguageCache.set(language, grammar);
70
+ return grammar;
71
+ }
72
+ catch {
73
+ optionalLanguageCache.set(language, null);
74
+ optionalAvailabilityCache.set(language, false);
75
+ return null;
76
+ }
77
+ };
78
+ const resolveLanguage = (key, language) => {
79
+ if (key in requiredLanguageMap) {
80
+ return requiredLanguageMap[key];
81
+ }
82
+ return loadOptionalLanguage(language);
83
+ };
84
+ export const isLanguageAvailable = (language) => language in requiredLanguageMap || isOptionalLanguageInstalled(language);
46
85
  export const loadParser = async () => {
47
86
  if (parser)
48
87
  return parser;
@@ -55,17 +94,20 @@ export const loadLanguage = async (language, filePath) => {
55
94
  const key = language === SupportedLanguages.TypeScript && filePath?.endsWith('.tsx')
56
95
  ? `${language}:tsx`
57
96
  : language;
58
- const lang = languageMap[key];
97
+ const lang = resolveLanguage(key, language);
59
98
  if (!lang) {
60
99
  throw new Error(`Unsupported language: ${language}`);
61
100
  }
62
101
  parser.setLanguage(lang);
63
102
  };
64
- const MAX_CHUNK = 4096;
65
103
  /**
66
- * Parse source code using tree-sitter's chunked callback API.
67
- * Avoids the native binding's single-buffer size limit (< 32768 bytes)
68
- * that causes "Invalid argument" errors on large files.
104
+ * Parse source code using tree-sitter's string input path with an adaptive
105
+ * native buffer size.
106
+ *
107
+ * The callback input API receives byte offsets. Returning JavaScript string
108
+ * slices from those byte offsets is unsafe for UTF-8/multi-byte content and has
109
+ * caused native tree-sitter crashes in large repositories. Use the stable string
110
+ * input path instead and raise tree-sitter's internal buffer for large files.
69
111
  *
70
112
  * @param content - Full source file content as UTF-8 string
71
113
  * @param oldTree - Optional previous tree for incremental parsing (must call tree.edit() first)
@@ -74,9 +116,6 @@ const MAX_CHUNK = 4096;
74
116
  export const parseContent = (content, oldTree) => {
75
117
  if (!parser)
76
118
  throw new Error('Parser not initialized — call loadParser() first');
77
- return parser.parse((index) => {
78
- if (index >= content.length)
79
- return null;
80
- return content.slice(index, index + MAX_CHUNK);
81
- }, oldTree);
119
+ const bufferSize = getTreeSitterBufferSize(Buffer.byteLength(content, 'utf8'));
120
+ return parser.parse(content, oldTree ?? null, { bufferSize });
82
121
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veewo/gitnexus",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -101,7 +101,8 @@
101
101
  },
102
102
  "optionalDependencies": {
103
103
  "tree-sitter-kotlin": "^0.3.8",
104
- "tree-sitter-swift": "^0.6.0"
104
+ "tree-sitter-swift": "^0.6.0",
105
+ "tree-sitter-gdscript": "^6.1.0"
105
106
  },
106
107
  "devDependencies": {
107
108
  "@types/cli-progress": "^3.11.6",
@@ -60,35 +60,83 @@ $GN analyze
60
60
 
61
61
  Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files.
62
62
 
63
- | Flag | Effect |
64
- | -------------------------- | ----------------------------------------------------------------------------------------- |
65
- | `--force` | Force full re-index even if up to date |
66
- | `--embeddings` | Enable embedding generation for semantic search (off by default) |
67
- | `--extensions <ext>` | Limit parsing to specific file types (comma-separated, e.g., `--extensions ".cs,.meta"`) |
68
- | `--csharp-define-csproj <path>` | Load C# `DefineConstants` from a `.csproj` and normalize `#if/#elif/#else/#endif` before parsing |
69
- | `--scope-prefix <prefix>` | Limit analysis to a path prefix (e.g., `--scope-prefix Assets/` for Unity) |
70
- | `--scope-manifest <file>` | Read scope rules from a manifest file (e.g., `.gitnexus/sync-manifest.txt`) |
71
- | `--sync-manifest-policy <policy>` | Drift policy when explicit CLI values differ from manifest directives: `ask|update|keep|error` |
72
- | `--skills` | Generate repo-specific skill files from detected code communities |
63
+ | Flag | Effect |
64
+ |------|--------|
65
+ | `--force` | Force full re-index even if up to date |
66
+ | `--embeddings` | Enable embedding generation (off by default) |
67
+ | `--skills` | Generate repo-specific skill files from detected communities |
73
68
 
74
- **Scope manifest syntax:** Non-`@` lines are path-prefix scope rules (same semantics as before). `@key=value` directives set analyze options: `@extensions=<csv>`, `@repoAlias=<name>`, `@embeddings=<true|false>`. Unknown directives fail fast.
69
+ **Two mutually exclusive paths. Choose one per rebuild.**
75
70
 
76
- **Defaulting + drift guard:** If `.gitnexus/sync-manifest.txt` exists and you do not pass `--scope-manifest`/`--scope-prefix`, analyze auto-uses that file. When explicit CLI values (`--extensions`, `--repo-alias`, `--embeddings`) differ from manifest directives, CLI follows `--sync-manifest-policy` (default `ask`; non-TTY requires explicit policy).
71
+ #### Path A: sync-manifest managed (recommended for Unity / monorepo)
77
72
 
78
- **When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook runs `analyze` automatically after `git commit` and `git merge`, preserving embeddings if previously generated.
73
+ If `.gitnexus/sync-manifest.txt` exists, `analyze` **auto-uses** it when you do **not** pass `--scope-prefix` or `--scope-manifest`.
79
74
 
80
- **Unity projects:** Add `--extensions ".cs,.meta"` to ensure Unity asset edges (`UNITY_ASSET_GUID_REF`, `UNITY_COMPONENT_INSTANCE`) are parsed. Add `--scope-prefix Assets/` to limit scope if all code lives under `Assets/`.
75
+ ```bash
76
+ # Unity project with an existing manifest — this is the normal rebuild command
77
+ $GN analyze --force
78
+ ```
79
+
80
+ The manifest controls scope rules, extensions, and repo alias. Example:
81
+
82
+ ```
83
+ Assets/
84
+ Packages/
85
+ @extensions=.cs,.meta
86
+ @repoAlias=neonspark-core
87
+ ```
88
+
89
+ - Non-`@` lines = path-prefix scope rules
90
+ - `@extensions=<csv>` = file extension filter
91
+ - `@repoAlias=<name>` = stable repo alias
92
+ - `@embeddings=<true|false>` = embedding toggle
93
+
94
+ **Drift guard:** If you pass `--extensions` / `--repo-alias` / `--embeddings` while a manifest exists, CLI compares them. Use `--sync-manifest-policy` to control: `ask|update|keep|error` (default `ask`; non-TTY requires explicit policy).
95
+
96
+ **Do not mix Path A and Path B.** Passing `--scope-prefix` or `--extensions` when a manifest exists triggers the drift guard and may error out in non-TTY environments.
97
+
98
+ #### Path B: manual CLI flags (first-time or simple projects)
81
99
 
82
- **C# conditional-compilation projects (recommended):**
100
+ Use when no sync-manifest exists:
83
101
 
84
- - Unity: pass `--csharp-define-csproj /path/to/Assembly-CSharp.csproj` (for neonspark, use `/Volumes/Shuttle/projects/neonspark/Assembly-CSharp.csproj`).
85
- - Non-Unity: discover candidate project files first, then pass the intended one explicitly:
102
+ ```bash
103
+ # Unity project, first-time index
104
+ $GN analyze --force --extensions ".cs,.meta" --scope-prefix Assets/ --repo-alias neonspark-core
105
+
106
+ # Generic project
107
+ $GN analyze --force --extensions ".ts,.tsx" --scope-prefix src/
108
+ ```
109
+
110
+ | Manual flag | Effect |
111
+ |-------------|--------|
112
+ | `--extensions <ext>` | Comma-separated file extensions |
113
+ | `--scope-prefix <prefix>` | Add a path prefix rule (repeatable) |
114
+ | `--scope-manifest <file>` | Read scope rules from a manifest file |
115
+ | `--repo-alias <name>` | Override indexed repository name |
116
+ | `--csharp-define-csproj <path>` | Load C# `DefineConstants` from `.csproj` for `#if` normalization |
117
+
118
+ **C# preprocessing (Unity):** For projects with heavy conditional compilation, add `--csharp-define-csproj /path/to/Assembly-CSharp.csproj` (neonspark: `/Volumes/Shuttle/projects/neonspark/Assembly-CSharp.csproj`). Without it, C# files are parsed raw and tree-sitter may mishandle `#if` branches.
119
+
120
+ #### Rebuild recovery — when analyze hangs or crashes
121
+
122
+ If `analyze --force` hangs (no progress after 5+ minutes) or crashes leaving a corrupted index:
86
123
 
87
124
  ```bash
88
- rg --files -g '*.csproj'
89
- $GN analyze --extensions ".cs" --csharp-define-csproj <picked-project>.csproj
125
+ # 1. Clean the corrupted index (preserves sync-manifest.txt)
126
+ $GN clean --force
127
+
128
+ # 2. Rebuild
129
+ $GN analyze --force
90
130
  ```
91
131
 
132
+ **When to clean before rebuild:**
133
+ - Previous run left `.gitnexus/csv/` with no `relations.csv` (crash mid-streaming)
134
+ - `.gitnexus/lbug.wal` exists but `lbug` is tiny (LadybugDB in unrecovered state)
135
+ - Any `analyze` run hangs indefinitely in the "Loading into LadybugDB..." phase
136
+ - After `gitnexus clean`, always run `analyze --force` to rebuild
137
+
138
+ **When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale.
139
+
92
140
  ### status — Check index freshness
93
141
 
94
142
  ```bash
@@ -97,13 +145,13 @@ $GN status
97
145
 
98
146
  Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed.
99
147
 
100
- ### clean — Delete the index
148
+ ### clean — Delete the index (preserves config)
101
149
 
102
150
  ```bash
103
- $GN clean
151
+ $GN clean --force
104
152
  ```
105
153
 
106
- Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project.
154
+ Removes the GitNexus index (graph, CSVs, LadybugDB) from `.gitnexus/` while **preserving `sync-manifest.txt`** and other configuration files. Use this to recover from a corrupted index before re-indexing.
107
155
 
108
156
  | Flag | Effect |
109
157
  | --------- | ------------------------------------------------- |
@@ -189,6 +237,7 @@ $GN unity-ui-trace "Assets/NEON/VeewoUI/Uxml/BarScreen/Patch/PatchItemPreview.ux
189
237
  - **"Not inside a git repository"**: Run from a directory inside a git repo
190
238
  - **Index is stale after re-analyzing**: Restart Claude Code to reload the MCP server
191
239
  - **Embeddings slow**: Omit `--embeddings` (it's off by default) or set `OPENAI_API_KEY` for faster API-based embedding
240
+ - **`analyze --force` hangs or crashes**: Run `$GN clean --force` to remove the corrupted index (sync-manifest is preserved), then `$GN analyze --force` to rebuild. Common corruption signatures: `.gitnexus/csv/` exists but `relations.csv` is missing; `.gitnexus/lbug.wal` exists while `lbug` is only a few KB.
192
241
 
193
242
  ## Runtime-Chain Closure Guard
194
243