@duytransipher/gitnexus 1.4.6-sipher.0

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 (224) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +261 -0
  3. package/dist/cli/ai-context.d.ts +23 -0
  4. package/dist/cli/ai-context.js +265 -0
  5. package/dist/cli/analyze.d.ts +12 -0
  6. package/dist/cli/analyze.js +345 -0
  7. package/dist/cli/augment.d.ts +13 -0
  8. package/dist/cli/augment.js +33 -0
  9. package/dist/cli/clean.d.ts +10 -0
  10. package/dist/cli/clean.js +60 -0
  11. package/dist/cli/eval-server.d.ts +37 -0
  12. package/dist/cli/eval-server.js +389 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +137 -0
  15. package/dist/cli/lazy-action.d.ts +6 -0
  16. package/dist/cli/lazy-action.js +18 -0
  17. package/dist/cli/list.d.ts +6 -0
  18. package/dist/cli/list.js +30 -0
  19. package/dist/cli/mcp.d.ts +8 -0
  20. package/dist/cli/mcp.js +36 -0
  21. package/dist/cli/serve.d.ts +4 -0
  22. package/dist/cli/serve.js +6 -0
  23. package/dist/cli/setup.d.ts +8 -0
  24. package/dist/cli/setup.js +367 -0
  25. package/dist/cli/sipher-patched.d.ts +2 -0
  26. package/dist/cli/sipher-patched.js +77 -0
  27. package/dist/cli/skill-gen.d.ts +26 -0
  28. package/dist/cli/skill-gen.js +549 -0
  29. package/dist/cli/status.d.ts +6 -0
  30. package/dist/cli/status.js +36 -0
  31. package/dist/cli/tool.d.ts +60 -0
  32. package/dist/cli/tool.js +180 -0
  33. package/dist/cli/wiki.d.ts +15 -0
  34. package/dist/cli/wiki.js +365 -0
  35. package/dist/config/ignore-service.d.ts +26 -0
  36. package/dist/config/ignore-service.js +284 -0
  37. package/dist/config/supported-languages.d.ts +15 -0
  38. package/dist/config/supported-languages.js +16 -0
  39. package/dist/core/augmentation/engine.d.ts +26 -0
  40. package/dist/core/augmentation/engine.js +240 -0
  41. package/dist/core/embeddings/embedder.d.ts +60 -0
  42. package/dist/core/embeddings/embedder.js +251 -0
  43. package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
  44. package/dist/core/embeddings/embedding-pipeline.js +356 -0
  45. package/dist/core/embeddings/index.d.ts +9 -0
  46. package/dist/core/embeddings/index.js +9 -0
  47. package/dist/core/embeddings/text-generator.d.ts +24 -0
  48. package/dist/core/embeddings/text-generator.js +182 -0
  49. package/dist/core/embeddings/types.d.ts +87 -0
  50. package/dist/core/embeddings/types.js +32 -0
  51. package/dist/core/graph/graph.d.ts +2 -0
  52. package/dist/core/graph/graph.js +66 -0
  53. package/dist/core/graph/types.d.ts +66 -0
  54. package/dist/core/graph/types.js +1 -0
  55. package/dist/core/ingestion/ast-cache.d.ts +11 -0
  56. package/dist/core/ingestion/ast-cache.js +35 -0
  57. package/dist/core/ingestion/call-processor.d.ts +23 -0
  58. package/dist/core/ingestion/call-processor.js +793 -0
  59. package/dist/core/ingestion/call-routing.d.ts +68 -0
  60. package/dist/core/ingestion/call-routing.js +129 -0
  61. package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
  62. package/dist/core/ingestion/cluster-enricher.js +170 -0
  63. package/dist/core/ingestion/community-processor.d.ts +39 -0
  64. package/dist/core/ingestion/community-processor.js +312 -0
  65. package/dist/core/ingestion/constants.d.ts +16 -0
  66. package/dist/core/ingestion/constants.js +16 -0
  67. package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
  68. package/dist/core/ingestion/entry-point-scoring.js +353 -0
  69. package/dist/core/ingestion/export-detection.d.ts +18 -0
  70. package/dist/core/ingestion/export-detection.js +231 -0
  71. package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
  72. package/dist/core/ingestion/filesystem-walker.js +81 -0
  73. package/dist/core/ingestion/framework-detection.d.ts +54 -0
  74. package/dist/core/ingestion/framework-detection.js +411 -0
  75. package/dist/core/ingestion/heritage-processor.d.ts +28 -0
  76. package/dist/core/ingestion/heritage-processor.js +251 -0
  77. package/dist/core/ingestion/import-processor.d.ts +34 -0
  78. package/dist/core/ingestion/import-processor.js +398 -0
  79. package/dist/core/ingestion/language-config.d.ts +46 -0
  80. package/dist/core/ingestion/language-config.js +167 -0
  81. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  82. package/dist/core/ingestion/mro-processor.js +369 -0
  83. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  84. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  85. package/dist/core/ingestion/parsing-processor.d.ts +19 -0
  86. package/dist/core/ingestion/parsing-processor.js +315 -0
  87. package/dist/core/ingestion/pipeline.d.ts +6 -0
  88. package/dist/core/ingestion/pipeline.js +401 -0
  89. package/dist/core/ingestion/process-processor.d.ts +51 -0
  90. package/dist/core/ingestion/process-processor.js +315 -0
  91. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  92. package/dist/core/ingestion/resolution-context.js +132 -0
  93. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  94. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  95. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  96. package/dist/core/ingestion/resolvers/go.js +42 -0
  97. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  98. package/dist/core/ingestion/resolvers/index.js +13 -0
  99. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  100. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  101. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  102. package/dist/core/ingestion/resolvers/php.js +35 -0
  103. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  104. package/dist/core/ingestion/resolvers/python.js +52 -0
  105. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  106. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  107. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  108. package/dist/core/ingestion/resolvers/rust.js +73 -0
  109. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  110. package/dist/core/ingestion/resolvers/standard.js +123 -0
  111. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  112. package/dist/core/ingestion/resolvers/utils.js +122 -0
  113. package/dist/core/ingestion/structure-processor.d.ts +2 -0
  114. package/dist/core/ingestion/structure-processor.js +36 -0
  115. package/dist/core/ingestion/symbol-table.d.ts +63 -0
  116. package/dist/core/ingestion/symbol-table.js +85 -0
  117. package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
  118. package/dist/core/ingestion/tree-sitter-queries.js +888 -0
  119. package/dist/core/ingestion/type-env.d.ts +49 -0
  120. package/dist/core/ingestion/type-env.js +613 -0
  121. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  123. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  124. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  125. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  126. package/dist/core/ingestion/type-extractors/go.js +467 -0
  127. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  128. package/dist/core/ingestion/type-extractors/index.js +31 -0
  129. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  130. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  131. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  132. package/dist/core/ingestion/type-extractors/php.js +549 -0
  133. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  134. package/dist/core/ingestion/type-extractors/python.js +455 -0
  135. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  136. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  137. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  138. package/dist/core/ingestion/type-extractors/rust.js +456 -0
  139. package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
  140. package/dist/core/ingestion/type-extractors/shared.js +810 -0
  141. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  142. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  143. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  144. package/dist/core/ingestion/type-extractors/types.js +1 -0
  145. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  146. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  147. package/dist/core/ingestion/utils.d.ts +138 -0
  148. package/dist/core/ingestion/utils.js +1290 -0
  149. package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
  150. package/dist/core/ingestion/workers/parse-worker.js +1126 -0
  151. package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
  152. package/dist/core/ingestion/workers/worker-pool.js +128 -0
  153. package/dist/core/lbug/csv-generator.d.ts +33 -0
  154. package/dist/core/lbug/csv-generator.js +366 -0
  155. package/dist/core/lbug/lbug-adapter.d.ts +103 -0
  156. package/dist/core/lbug/lbug-adapter.js +769 -0
  157. package/dist/core/lbug/schema.d.ts +53 -0
  158. package/dist/core/lbug/schema.js +430 -0
  159. package/dist/core/search/bm25-index.d.ts +23 -0
  160. package/dist/core/search/bm25-index.js +96 -0
  161. package/dist/core/search/hybrid-search.d.ts +49 -0
  162. package/dist/core/search/hybrid-search.js +118 -0
  163. package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
  164. package/dist/core/tree-sitter/parser-loader.js +63 -0
  165. package/dist/core/wiki/generator.d.ts +120 -0
  166. package/dist/core/wiki/generator.js +939 -0
  167. package/dist/core/wiki/graph-queries.d.ts +80 -0
  168. package/dist/core/wiki/graph-queries.js +238 -0
  169. package/dist/core/wiki/html-viewer.d.ts +10 -0
  170. package/dist/core/wiki/html-viewer.js +297 -0
  171. package/dist/core/wiki/llm-client.d.ts +43 -0
  172. package/dist/core/wiki/llm-client.js +186 -0
  173. package/dist/core/wiki/prompts.d.ts +53 -0
  174. package/dist/core/wiki/prompts.js +174 -0
  175. package/dist/lib/utils.d.ts +1 -0
  176. package/dist/lib/utils.js +3 -0
  177. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  178. package/dist/mcp/compatible-stdio-transport.js +200 -0
  179. package/dist/mcp/core/embedder.d.ts +27 -0
  180. package/dist/mcp/core/embedder.js +108 -0
  181. package/dist/mcp/core/lbug-adapter.d.ts +57 -0
  182. package/dist/mcp/core/lbug-adapter.js +455 -0
  183. package/dist/mcp/local/local-backend.d.ts +181 -0
  184. package/dist/mcp/local/local-backend.js +1722 -0
  185. package/dist/mcp/resources.d.ts +31 -0
  186. package/dist/mcp/resources.js +411 -0
  187. package/dist/mcp/server.d.ts +23 -0
  188. package/dist/mcp/server.js +296 -0
  189. package/dist/mcp/staleness.d.ts +15 -0
  190. package/dist/mcp/staleness.js +29 -0
  191. package/dist/mcp/tools.d.ts +24 -0
  192. package/dist/mcp/tools.js +292 -0
  193. package/dist/server/api.d.ts +10 -0
  194. package/dist/server/api.js +344 -0
  195. package/dist/server/mcp-http.d.ts +13 -0
  196. package/dist/server/mcp-http.js +100 -0
  197. package/dist/storage/git.d.ts +6 -0
  198. package/dist/storage/git.js +35 -0
  199. package/dist/storage/repo-manager.d.ts +138 -0
  200. package/dist/storage/repo-manager.js +299 -0
  201. package/dist/types/pipeline.d.ts +32 -0
  202. package/dist/types/pipeline.js +18 -0
  203. package/dist/unreal/bridge.d.ts +4 -0
  204. package/dist/unreal/bridge.js +113 -0
  205. package/dist/unreal/config.d.ts +6 -0
  206. package/dist/unreal/config.js +55 -0
  207. package/dist/unreal/types.d.ts +105 -0
  208. package/dist/unreal/types.js +1 -0
  209. package/hooks/claude/gitnexus-hook.cjs +238 -0
  210. package/hooks/claude/pre-tool-use.sh +79 -0
  211. package/hooks/claude/session-start.sh +42 -0
  212. package/package.json +100 -0
  213. package/scripts/ensure-cli-executable.cjs +21 -0
  214. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  215. package/scripts/setup-unreal-gitnexus.ps1 +191 -0
  216. package/skills/gitnexus-cli.md +82 -0
  217. package/skills/gitnexus-debugging.md +89 -0
  218. package/skills/gitnexus-exploring.md +78 -0
  219. package/skills/gitnexus-guide.md +64 -0
  220. package/skills/gitnexus-impact-analysis.md +97 -0
  221. package/skills/gitnexus-pr-review.md +163 -0
  222. package/skills/gitnexus-refactoring.md +121 -0
  223. package/vendor/leiden/index.cjs +355 -0
  224. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,1290 @@
1
+ import { SupportedLanguages } from '../../config/supported-languages.js';
2
+ import { generateId } from '../../lib/utils.js';
3
+ /**
4
+ * Ordered list of definition capture keys for tree-sitter query matches.
5
+ * Used to extract the definition node from a capture map.
6
+ */
7
+ export 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
+ ];
31
+ /** Extract the definition node from a tree-sitter query capture map. */
32
+ export const getDefinitionNodeFromCaptures = (captureMap) => {
33
+ for (const key of DEFINITION_CAPTURE_KEYS) {
34
+ if (captureMap[key])
35
+ return captureMap[key];
36
+ }
37
+ return null;
38
+ };
39
+ /**
40
+ * Node types that represent function/method definitions across languages.
41
+ * Used to find the enclosing function for a call site.
42
+ */
43
+ export const FUNCTION_NODE_TYPES = new Set([
44
+ // TypeScript/JavaScript
45
+ 'function_declaration',
46
+ 'arrow_function',
47
+ 'function_expression',
48
+ 'method_definition',
49
+ 'generator_function_declaration',
50
+ // Python
51
+ 'function_definition',
52
+ // Common async variants
53
+ 'async_function_declaration',
54
+ 'async_arrow_function',
55
+ // Java
56
+ 'method_declaration',
57
+ 'constructor_declaration',
58
+ // C/C++
59
+ // 'function_definition' already included above
60
+ // Go
61
+ // 'method_declaration' already included from Java
62
+ // C#
63
+ 'local_function_statement',
64
+ // Rust
65
+ 'function_item',
66
+ 'impl_item', // Methods inside impl blocks
67
+ // PHP
68
+ 'anonymous_function',
69
+ // Kotlin
70
+ 'lambda_literal',
71
+ // Swift
72
+ 'init_declaration',
73
+ 'deinit_declaration',
74
+ // Ruby
75
+ 'method', // def foo
76
+ 'singleton_method', // def self.foo
77
+ ]);
78
+ /**
79
+ * Node types for standard function declarations that need C/C++ declarator handling.
80
+ * Used by extractFunctionName to determine how to extract the function name.
81
+ */
82
+ export const FUNCTION_DECLARATION_TYPES = new Set([
83
+ 'function_declaration',
84
+ 'function_definition',
85
+ 'async_function_declaration',
86
+ 'generator_function_declaration',
87
+ 'function_item',
88
+ ]);
89
+ /**
90
+ * Built-in function/method names that should not be tracked as call targets.
91
+ * Covers JS/TS, Python, Kotlin, C/C++, PHP, Swift standard library functions.
92
+ */
93
+ export const BUILT_IN_NAMES = new Set([
94
+ // JavaScript/TypeScript
95
+ 'console', 'log', 'warn', 'error', 'info', 'debug',
96
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
97
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
98
+ 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
99
+ 'JSON', 'parse', 'stringify',
100
+ 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
101
+ 'Map', 'Set', 'WeakMap', 'WeakSet',
102
+ 'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
103
+ 'Math', 'Date', 'RegExp', 'Error',
104
+ 'require', 'import', 'export', 'fetch', 'Response', 'Request',
105
+ 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
106
+ 'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
107
+ 'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
108
+ 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
109
+ 'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
110
+ 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
111
+ 'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
112
+ 'hasOwnProperty', 'toString', 'valueOf',
113
+ // Python
114
+ 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
115
+ 'append', 'extend', 'update',
116
+ // NOTE: 'open', 'read', 'write', 'close' removed — these are real C POSIX syscalls
117
+ 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
118
+ 'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
119
+ // Kotlin stdlib
120
+ 'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error',
121
+ 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
122
+ 'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless',
123
+ 'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet',
124
+ 'repeat', 'synchronized',
125
+ // Kotlin coroutine builders & scope functions
126
+ 'launch', 'async', 'runBlocking', 'withContext', 'coroutineScope',
127
+ 'supervisorScope', 'delay',
128
+ // Kotlin Flow operators
129
+ 'flow', 'flowOf', 'collect', 'emit', 'onEach', 'catch',
130
+ 'buffer', 'conflate', 'distinctUntilChanged',
131
+ 'flatMapLatest', 'flatMapMerge', 'combine',
132
+ 'stateIn', 'shareIn', 'launchIn',
133
+ // Kotlin infix stdlib functions
134
+ 'to', 'until', 'downTo', 'step',
135
+ // C/C++ standard library
136
+ 'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
137
+ 'scanf', 'fscanf', 'sscanf',
138
+ 'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'memcmp',
139
+ 'strlen', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strstr', 'strchr', 'strrchr',
140
+ 'atoi', 'atol', 'atof', 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod',
141
+ 'sizeof', 'offsetof', 'typeof',
142
+ 'assert', 'abort', 'exit', '_exit',
143
+ 'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind', 'fflush', 'fgets', 'fputs',
144
+ // Linux kernel common macros/helpers (not real call targets)
145
+ 'likely', 'unlikely', 'BUG', 'BUG_ON', 'WARN', 'WARN_ON', 'WARN_ONCE',
146
+ 'IS_ERR', 'PTR_ERR', 'ERR_PTR', 'IS_ERR_OR_NULL',
147
+ 'ARRAY_SIZE', 'container_of', 'list_for_each_entry', 'list_for_each_entry_safe',
148
+ 'min', 'max', 'clamp', 'abs', 'swap',
149
+ 'pr_info', 'pr_warn', 'pr_err', 'pr_debug', 'pr_notice', 'pr_crit', 'pr_emerg',
150
+ 'printk', 'dev_info', 'dev_warn', 'dev_err', 'dev_dbg',
151
+ 'GFP_KERNEL', 'GFP_ATOMIC',
152
+ 'spin_lock', 'spin_unlock', 'spin_lock_irqsave', 'spin_unlock_irqrestore',
153
+ 'mutex_lock', 'mutex_unlock', 'mutex_init',
154
+ 'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
155
+ 'get', 'put',
156
+ // C# / .NET built-ins
157
+ 'Console', 'WriteLine', 'ReadLine', 'Write',
158
+ 'Task', 'Run', 'Wait', 'WhenAll', 'WhenAny', 'FromResult', 'Delay', 'ContinueWith',
159
+ 'ConfigureAwait', 'GetAwaiter', 'GetResult',
160
+ 'ToString', 'GetType', 'Equals', 'GetHashCode', 'ReferenceEquals',
161
+ 'Add', 'Remove', 'Contains', 'Clear', 'Count', 'Any', 'All',
162
+ 'Where', 'Select', 'SelectMany', 'OrderBy', 'OrderByDescending', 'GroupBy',
163
+ 'First', 'FirstOrDefault', 'Single', 'SingleOrDefault', 'Last', 'LastOrDefault',
164
+ 'ToList', 'ToArray', 'ToDictionary', 'AsEnumerable', 'AsQueryable',
165
+ 'Aggregate', 'Sum', 'Average', 'Min', 'Max', 'Distinct', 'Skip', 'Take',
166
+ 'String', 'Format', 'IsNullOrEmpty', 'IsNullOrWhiteSpace', 'Concat', 'Join',
167
+ 'Trim', 'TrimStart', 'TrimEnd', 'Split', 'Replace', 'StartsWith', 'EndsWith',
168
+ 'Convert', 'ToInt32', 'ToDouble', 'ToBoolean', 'ToByte',
169
+ 'Math', 'Abs', 'Ceiling', 'Floor', 'Round', 'Pow', 'Sqrt',
170
+ 'Dispose', 'Close',
171
+ 'TryParse', 'Parse',
172
+ 'AddRange', 'RemoveAt', 'RemoveAll', 'FindAll', 'Exists', 'TrueForAll',
173
+ 'ContainsKey', 'TryGetValue', 'AddOrUpdate',
174
+ 'Throw', 'ThrowIfNull',
175
+ // PHP built-ins
176
+ 'echo', 'isset', 'empty', 'unset', 'list', 'array', 'compact', 'extract',
177
+ 'count', 'strlen', 'strpos', 'strrpos', 'substr', 'strtolower', 'strtoupper', 'trim',
178
+ 'ltrim', 'rtrim', 'str_replace', 'str_contains', 'str_starts_with', 'str_ends_with',
179
+ 'sprintf', 'vsprintf', 'printf', 'number_format',
180
+ 'array_map', 'array_filter', 'array_reduce', 'array_push', 'array_pop', 'array_shift',
181
+ 'array_unshift', 'array_slice', 'array_splice', 'array_merge', 'array_keys', 'array_values',
182
+ 'array_key_exists', 'in_array', 'array_search', 'array_unique', 'usort', 'rsort',
183
+ 'json_encode', 'json_decode', 'serialize', 'unserialize',
184
+ 'intval', 'floatval', 'strval', 'boolval', 'is_null', 'is_string', 'is_int', 'is_array',
185
+ 'is_object', 'is_numeric', 'is_bool', 'is_float',
186
+ 'var_dump', 'print_r', 'var_export',
187
+ 'date', 'time', 'strtotime', 'mktime', 'microtime',
188
+ 'file_exists', 'file_get_contents', 'file_put_contents', 'is_file', 'is_dir',
189
+ 'preg_match', 'preg_match_all', 'preg_replace', 'preg_split',
190
+ 'header', 'session_start', 'session_destroy', 'ob_start', 'ob_end_clean', 'ob_get_clean',
191
+ 'dd', 'dump',
192
+ // Swift/iOS built-ins and standard library
193
+ 'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
194
+ 'assert', 'assertionFailure', 'NSLog',
195
+ 'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
196
+ 'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
197
+ 'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
198
+ 'type', 'MemoryLayout',
199
+ // Swift collection/string methods (common noise)
200
+ 'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
201
+ 'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
202
+ 'sorted', 'reversed', 'enumerated', 'joined', 'split',
203
+ 'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
204
+ 'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
205
+ // UIKit/Foundation common methods (noise in call graph)
206
+ 'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
207
+ 'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
208
+ 'addTarget', 'removeTarget', 'addGestureRecognizer',
209
+ 'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
210
+ 'NSLocalizedString', 'Bundle',
211
+ 'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
212
+ 'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
213
+ 'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
214
+ 'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
215
+ 'performSegue', 'prepare',
216
+ // GCD / async
217
+ 'DispatchQueue', 'async', 'sync', 'asyncAfter',
218
+ 'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
219
+ // Combine
220
+ 'sink', 'store', 'assign', 'receive', 'subscribe',
221
+ // Notification / KVO
222
+ 'addObserver', 'removeObserver', 'post', 'NotificationCenter',
223
+ // Rust standard library (common noise in call graphs)
224
+ 'unwrap', 'expect', 'unwrap_or', 'unwrap_or_else', 'unwrap_or_default',
225
+ 'ok', 'err', 'is_ok', 'is_err', 'map', 'map_err', 'and_then', 'or_else',
226
+ 'clone', 'to_string', 'to_owned', 'into', 'from', 'as_ref', 'as_mut',
227
+ 'iter', 'into_iter', 'collect', 'map', 'filter', 'fold', 'for_each',
228
+ 'len', 'is_empty', 'push', 'pop', 'insert', 'remove', 'contains',
229
+ 'format', 'write', 'writeln', 'panic', 'unreachable', 'todo', 'unimplemented',
230
+ 'vec', 'println', 'eprintln', 'dbg',
231
+ 'lock', 'read', 'write', 'try_lock',
232
+ 'spawn', 'join', 'sleep',
233
+ 'Some', 'None', 'Ok', 'Err',
234
+ // Ruby built-ins and Kernel methods
235
+ 'puts', 'p', 'pp', 'raise', 'fail',
236
+ 'require', 'require_relative', 'load', 'autoload',
237
+ 'include', 'extend', 'prepend',
238
+ 'attr_accessor', 'attr_reader', 'attr_writer',
239
+ 'public', 'private', 'protected', 'module_function',
240
+ 'lambda', 'proc', 'block_given?',
241
+ 'nil?', 'is_a?', 'kind_of?', 'instance_of?', 'respond_to?',
242
+ 'freeze', 'frozen?', 'dup', 'tap', 'yield_self',
243
+ // Ruby enumerables
244
+ 'each', 'select', 'reject', 'detect', 'collect',
245
+ 'inject', 'flat_map', 'each_with_object', 'each_with_index',
246
+ 'any?', 'all?', 'none?', 'count', 'first', 'last',
247
+ 'sort_by', 'min_by', 'max_by',
248
+ 'group_by', 'partition', 'compact', 'flatten', 'uniq',
249
+ ]);
250
+ /** Check if a name is a built-in function or common noise that should be filtered out */
251
+ export const isBuiltInOrNoise = (name) => BUILT_IN_NAMES.has(name);
252
+ /** AST node types that represent a class-like container (for HAS_METHOD edge extraction) */
253
+ export const CLASS_CONTAINER_TYPES = new Set([
254
+ 'class_declaration', 'abstract_class_declaration',
255
+ 'interface_declaration', 'struct_declaration', 'record_declaration',
256
+ 'class_specifier', 'struct_specifier',
257
+ 'impl_item', 'trait_item', 'struct_item', 'enum_item',
258
+ 'class_definition',
259
+ 'trait_declaration',
260
+ 'protocol_declaration',
261
+ // Ruby
262
+ 'class',
263
+ 'module',
264
+ // Kotlin
265
+ 'object_declaration',
266
+ 'companion_object',
267
+ ]);
268
+ export const CONTAINER_TYPE_TO_LABEL = {
269
+ class_declaration: 'Class',
270
+ abstract_class_declaration: 'Class',
271
+ interface_declaration: 'Interface',
272
+ struct_declaration: 'Struct',
273
+ struct_specifier: 'Struct',
274
+ class_specifier: 'Class',
275
+ class_definition: 'Class',
276
+ impl_item: 'Impl',
277
+ trait_item: 'Trait',
278
+ struct_item: 'Struct',
279
+ enum_item: 'Enum',
280
+ trait_declaration: 'Trait',
281
+ record_declaration: 'Record',
282
+ protocol_declaration: 'Interface',
283
+ class: 'Class',
284
+ module: 'Module',
285
+ object_declaration: 'Class',
286
+ companion_object: 'Class',
287
+ };
288
+ /** Walk up AST to find enclosing class/struct/interface/impl, return its generateId or null.
289
+ * For Go method_declaration nodes, extracts receiver type (e.g. `func (u *User) Save()` → User struct). */
290
+ export const findEnclosingClassId = (node, filePath) => {
291
+ let current = node.parent;
292
+ while (current) {
293
+ // Go: method_declaration has a receiver parameter with the struct type
294
+ if (current.type === 'method_declaration') {
295
+ const receiver = current.childForFieldName?.('receiver');
296
+ if (receiver) {
297
+ // receiver is a parameter_list: (u *User) or (u User)
298
+ const paramDecl = receiver.namedChildren?.find?.((c) => c.type === 'parameter_declaration');
299
+ if (paramDecl) {
300
+ const typeNode = paramDecl.childForFieldName?.('type');
301
+ if (typeNode) {
302
+ // Unwrap pointer_type (*User → User)
303
+ const inner = typeNode.type === 'pointer_type' ? typeNode.firstNamedChild : typeNode;
304
+ if (inner && (inner.type === 'type_identifier' || inner.type === 'identifier')) {
305
+ return generateId('Struct', `${filePath}:${inner.text}`);
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ // Go: type_declaration wrapping a struct_type (type User struct { ... })
312
+ // field_declaration → field_declaration_list → struct_type → type_spec → type_declaration
313
+ if (current.type === 'type_declaration') {
314
+ const typeSpec = current.children?.find((c) => c.type === 'type_spec');
315
+ if (typeSpec) {
316
+ const typeBody = typeSpec.childForFieldName?.('type');
317
+ if (typeBody?.type === 'struct_type' || typeBody?.type === 'interface_type') {
318
+ const nameNode = typeSpec.childForFieldName?.('name');
319
+ if (nameNode) {
320
+ const label = typeBody.type === 'struct_type' ? 'Struct' : 'Interface';
321
+ return generateId(label, `${filePath}:${nameNode.text}`);
322
+ }
323
+ }
324
+ }
325
+ }
326
+ if (CLASS_CONTAINER_TYPES.has(current.type)) {
327
+ // Rust impl_item: for `impl Trait for Struct {}`, pick the type after `for`
328
+ if (current.type === 'impl_item') {
329
+ const children = current.children ?? [];
330
+ const forIdx = children.findIndex((c) => c.text === 'for');
331
+ if (forIdx !== -1) {
332
+ const nameNode = children.slice(forIdx + 1).find((c) => c.type === 'type_identifier' || c.type === 'identifier');
333
+ if (nameNode) {
334
+ return generateId('Impl', `${filePath}:${nameNode.text}`);
335
+ }
336
+ }
337
+ // Fall through: plain `impl Struct {}` — use first type_identifier below
338
+ }
339
+ const nameNode = current.childForFieldName?.('name')
340
+ ?? current.children?.find((c) => c.type === 'type_identifier' || c.type === 'identifier' || c.type === 'name' || c.type === 'constant');
341
+ if (nameNode) {
342
+ const label = CONTAINER_TYPE_TO_LABEL[current.type] || 'Class';
343
+ return generateId(label, `${filePath}:${nameNode.text}`);
344
+ }
345
+ }
346
+ current = current.parent;
347
+ }
348
+ return null;
349
+ };
350
+ /**
351
+ * Extract function name and label from a function_definition or similar AST node.
352
+ * Handles C/C++ qualified_identifier (ClassName::MethodName) and other language patterns.
353
+ */
354
+ export const extractFunctionName = (node) => {
355
+ let funcName = null;
356
+ let label = 'Function';
357
+ // Swift init/deinit
358
+ if (node.type === 'init_declaration' || node.type === 'deinit_declaration') {
359
+ return {
360
+ funcName: node.type === 'init_declaration' ? 'init' : 'deinit',
361
+ label: 'Constructor',
362
+ };
363
+ }
364
+ if (FUNCTION_DECLARATION_TYPES.has(node.type)) {
365
+ // C/C++: function_definition -> [pointer_declarator ->] function_declarator -> qualified_identifier/identifier
366
+ // Unwrap pointer_declarator / reference_declarator wrappers to reach function_declarator
367
+ let declarator = node.childForFieldName?.('declarator');
368
+ if (!declarator) {
369
+ for (let i = 0; i < node.childCount; i++) {
370
+ const c = node.child(i);
371
+ if (c?.type === 'function_declarator') {
372
+ declarator = c;
373
+ break;
374
+ }
375
+ }
376
+ }
377
+ while (declarator && (declarator.type === 'pointer_declarator' || declarator.type === 'reference_declarator')) {
378
+ let nextDeclarator = declarator.childForFieldName?.('declarator');
379
+ if (!nextDeclarator) {
380
+ for (let i = 0; i < declarator.childCount; i++) {
381
+ const c = declarator.child(i);
382
+ if (c?.type === 'function_declarator' || c?.type === 'pointer_declarator' || c?.type === 'reference_declarator') {
383
+ nextDeclarator = c;
384
+ break;
385
+ }
386
+ }
387
+ }
388
+ declarator = nextDeclarator;
389
+ }
390
+ if (declarator) {
391
+ let innerDeclarator = declarator.childForFieldName?.('declarator');
392
+ if (!innerDeclarator) {
393
+ for (let i = 0; i < declarator.childCount; i++) {
394
+ const c = declarator.child(i);
395
+ if (c?.type === 'qualified_identifier' || c?.type === 'identifier' || c?.type === 'parenthesized_declarator') {
396
+ innerDeclarator = c;
397
+ break;
398
+ }
399
+ }
400
+ }
401
+ if (innerDeclarator?.type === 'qualified_identifier') {
402
+ let nameNode = innerDeclarator.childForFieldName?.('name');
403
+ if (!nameNode) {
404
+ for (let i = 0; i < innerDeclarator.childCount; i++) {
405
+ const c = innerDeclarator.child(i);
406
+ if (c?.type === 'identifier') {
407
+ nameNode = c;
408
+ break;
409
+ }
410
+ }
411
+ }
412
+ if (nameNode?.text) {
413
+ funcName = nameNode.text;
414
+ label = 'Method';
415
+ }
416
+ }
417
+ else if (innerDeclarator?.type === 'identifier') {
418
+ funcName = innerDeclarator.text;
419
+ }
420
+ else if (innerDeclarator?.type === 'parenthesized_declarator') {
421
+ let nestedId = null;
422
+ for (let i = 0; i < innerDeclarator.childCount; i++) {
423
+ const c = innerDeclarator.child(i);
424
+ if (c?.type === 'qualified_identifier' || c?.type === 'identifier') {
425
+ nestedId = c;
426
+ break;
427
+ }
428
+ }
429
+ if (nestedId?.type === 'qualified_identifier') {
430
+ let nameNode = nestedId.childForFieldName?.('name');
431
+ if (!nameNode) {
432
+ for (let i = 0; i < nestedId.childCount; i++) {
433
+ const c = nestedId.child(i);
434
+ if (c?.type === 'identifier') {
435
+ nameNode = c;
436
+ break;
437
+ }
438
+ }
439
+ }
440
+ if (nameNode?.text) {
441
+ funcName = nameNode.text;
442
+ label = 'Method';
443
+ }
444
+ }
445
+ else if (nestedId?.type === 'identifier') {
446
+ funcName = nestedId.text;
447
+ }
448
+ }
449
+ }
450
+ // Fallback for other languages (Kotlin uses simple_identifier, Swift uses simple_identifier)
451
+ if (!funcName) {
452
+ let nameNode = node.childForFieldName?.('name');
453
+ if (!nameNode) {
454
+ for (let i = 0; i < node.childCount; i++) {
455
+ const c = node.child(i);
456
+ if (c?.type === 'identifier' || c?.type === 'property_identifier' || c?.type === 'simple_identifier') {
457
+ nameNode = c;
458
+ break;
459
+ }
460
+ }
461
+ }
462
+ funcName = nameNode?.text;
463
+ }
464
+ }
465
+ else if (node.type === 'impl_item') {
466
+ let funcItem = null;
467
+ for (let i = 0; i < node.childCount; i++) {
468
+ const c = node.child(i);
469
+ if (c?.type === 'function_item') {
470
+ funcItem = c;
471
+ break;
472
+ }
473
+ }
474
+ if (funcItem) {
475
+ let nameNode = funcItem.childForFieldName?.('name');
476
+ if (!nameNode) {
477
+ for (let i = 0; i < funcItem.childCount; i++) {
478
+ const c = funcItem.child(i);
479
+ if (c?.type === 'identifier') {
480
+ nameNode = c;
481
+ break;
482
+ }
483
+ }
484
+ }
485
+ funcName = nameNode?.text;
486
+ label = 'Method';
487
+ }
488
+ }
489
+ else if (node.type === 'method_definition') {
490
+ let nameNode = node.childForFieldName?.('name');
491
+ if (!nameNode) {
492
+ for (let i = 0; i < node.childCount; i++) {
493
+ const c = node.child(i);
494
+ if (c?.type === 'property_identifier') {
495
+ nameNode = c;
496
+ break;
497
+ }
498
+ }
499
+ }
500
+ funcName = nameNode?.text;
501
+ label = 'Method';
502
+ }
503
+ else if (node.type === 'method_declaration' || node.type === 'constructor_declaration') {
504
+ let nameNode = node.childForFieldName?.('name');
505
+ if (!nameNode) {
506
+ for (let i = 0; i < node.childCount; i++) {
507
+ const c = node.child(i);
508
+ if (c?.type === 'identifier') {
509
+ nameNode = c;
510
+ break;
511
+ }
512
+ }
513
+ }
514
+ funcName = nameNode?.text;
515
+ label = 'Method';
516
+ }
517
+ else if (node.type === 'arrow_function' || node.type === 'function_expression') {
518
+ const parent = node.parent;
519
+ if (parent?.type === 'variable_declarator') {
520
+ let nameNode = parent.childForFieldName?.('name');
521
+ if (!nameNode) {
522
+ for (let i = 0; i < parent.childCount; i++) {
523
+ const c = parent.child(i);
524
+ if (c?.type === 'identifier') {
525
+ nameNode = c;
526
+ break;
527
+ }
528
+ }
529
+ }
530
+ funcName = nameNode?.text;
531
+ }
532
+ }
533
+ else if (node.type === 'method' || node.type === 'singleton_method') {
534
+ let nameNode = node.childForFieldName?.('name');
535
+ if (!nameNode) {
536
+ for (let i = 0; i < node.childCount; i++) {
537
+ const c = node.child(i);
538
+ if (c?.type === 'identifier') {
539
+ nameNode = c;
540
+ break;
541
+ }
542
+ }
543
+ }
544
+ funcName = nameNode?.text;
545
+ label = 'Method';
546
+ }
547
+ return { funcName, label };
548
+ };
549
+ /**
550
+ * Yield control to the event loop so spinners/progress can render.
551
+ * Call periodically in hot loops to prevent UI freezes.
552
+ */
553
+ export const yieldToEventLoop = () => new Promise(resolve => setImmediate(resolve));
554
+ /** Ruby extensionless filenames recognised as Ruby source */
555
+ const RUBY_EXTENSIONLESS_FILES = new Set(['Rakefile', 'Gemfile', 'Guardfile', 'Vagrantfile', 'Brewfile']);
556
+ /**
557
+ * Find a child of `childType` within a sibling node of `siblingType`.
558
+ * Used for Kotlin AST traversal where visibility_modifier lives inside a modifiers sibling.
559
+ */
560
+ export const findSiblingChild = (parent, siblingType, childType) => {
561
+ for (let i = 0; i < parent.childCount; i++) {
562
+ const sibling = parent.child(i);
563
+ if (sibling?.type === siblingType) {
564
+ for (let j = 0; j < sibling.childCount; j++) {
565
+ const child = sibling.child(j);
566
+ if (child?.type === childType)
567
+ return child;
568
+ }
569
+ }
570
+ }
571
+ return null;
572
+ };
573
+ /**
574
+ * Map file extension to SupportedLanguage enum
575
+ */
576
+ export const getLanguageFromFilename = (filename) => {
577
+ // TypeScript (including TSX)
578
+ if (filename.endsWith('.tsx'))
579
+ return SupportedLanguages.TypeScript;
580
+ if (filename.endsWith('.ts'))
581
+ return SupportedLanguages.TypeScript;
582
+ // JavaScript (including JSX)
583
+ if (filename.endsWith('.jsx'))
584
+ return SupportedLanguages.JavaScript;
585
+ if (filename.endsWith('.js'))
586
+ return SupportedLanguages.JavaScript;
587
+ // Python
588
+ if (filename.endsWith('.py'))
589
+ return SupportedLanguages.Python;
590
+ // Java
591
+ if (filename.endsWith('.java'))
592
+ return SupportedLanguages.Java;
593
+ // C source files
594
+ if (filename.endsWith('.c'))
595
+ return SupportedLanguages.C;
596
+ // C++ (all common extensions, including .h)
597
+ // .h is parsed as C++ because tree-sitter-cpp is a strict superset of C, so pure-C
598
+ // headers parse correctly, and C++ headers (classes, templates) are handled properly.
599
+ if (filename.endsWith('.cpp') || filename.endsWith('.cc') || filename.endsWith('.cxx') ||
600
+ filename.endsWith('.h') || filename.endsWith('.hpp') || filename.endsWith('.hxx') || filename.endsWith('.hh'))
601
+ return SupportedLanguages.CPlusPlus;
602
+ // C#
603
+ if (filename.endsWith('.cs'))
604
+ return SupportedLanguages.CSharp;
605
+ // Go
606
+ if (filename.endsWith('.go'))
607
+ return SupportedLanguages.Go;
608
+ // Rust
609
+ if (filename.endsWith('.rs'))
610
+ return SupportedLanguages.Rust;
611
+ // Kotlin
612
+ if (filename.endsWith('.kt') || filename.endsWith('.kts'))
613
+ return SupportedLanguages.Kotlin;
614
+ // PHP (all common extensions)
615
+ if (filename.endsWith('.php') || filename.endsWith('.phtml') ||
616
+ filename.endsWith('.php3') || filename.endsWith('.php4') ||
617
+ filename.endsWith('.php5') || filename.endsWith('.php8')) {
618
+ return SupportedLanguages.PHP;
619
+ }
620
+ // Ruby (extensions)
621
+ if (filename.endsWith('.rb') || filename.endsWith('.rake') || filename.endsWith('.gemspec')) {
622
+ return SupportedLanguages.Ruby;
623
+ }
624
+ // Ruby (extensionless files)
625
+ const basename = filename.split('/').pop() || filename;
626
+ if (RUBY_EXTENSIONLESS_FILES.has(basename)) {
627
+ return SupportedLanguages.Ruby;
628
+ }
629
+ // Swift (extensions)
630
+ if (filename.endsWith('.swift'))
631
+ return SupportedLanguages.Swift;
632
+ return null;
633
+ };
634
+ const CALL_ARGUMENT_LIST_TYPES = new Set([
635
+ 'arguments',
636
+ 'argument_list',
637
+ 'value_arguments',
638
+ ]);
639
+ /**
640
+ * Extract parameter count and return type text from an AST method/function node.
641
+ * Works across languages by looking for common AST patterns.
642
+ */
643
+ export const extractMethodSignature = (node) => {
644
+ let parameterCount = 0;
645
+ let returnType;
646
+ let isVariadic = false;
647
+ if (!node)
648
+ return { parameterCount, returnType };
649
+ const paramListTypes = new Set([
650
+ 'formal_parameters', 'parameters', 'parameter_list',
651
+ 'function_parameters', 'method_parameters', 'function_value_parameters',
652
+ ]);
653
+ // Node types that indicate variadic/rest parameters
654
+ const VARIADIC_PARAM_TYPES = new Set([
655
+ 'variadic_parameter_declaration', // Go: ...string
656
+ 'variadic_parameter', // Rust: extern "C" fn(...)
657
+ 'spread_parameter', // Java: Object... args
658
+ 'list_splat_pattern', // Python: *args
659
+ 'dictionary_splat_pattern', // Python: **kwargs
660
+ ]);
661
+ const findParameterList = (current) => {
662
+ for (const child of current.children) {
663
+ if (paramListTypes.has(child.type))
664
+ return child;
665
+ }
666
+ for (const child of current.children) {
667
+ const nested = findParameterList(child);
668
+ if (nested)
669
+ return nested;
670
+ }
671
+ return null;
672
+ };
673
+ const parameterList = (paramListTypes.has(node.type) ? node // node itself IS the parameter list (e.g. C# primary constructors)
674
+ : node.childForFieldName?.('parameters')
675
+ ?? findParameterList(node));
676
+ if (parameterList && paramListTypes.has(parameterList.type)) {
677
+ for (const param of parameterList.namedChildren) {
678
+ if (param.type === 'comment')
679
+ continue;
680
+ if (param.text === 'self' || param.text === '&self' || param.text === '&mut self' ||
681
+ param.type === 'self_parameter') {
682
+ continue;
683
+ }
684
+ // Check for variadic parameter types
685
+ if (VARIADIC_PARAM_TYPES.has(param.type)) {
686
+ isVariadic = true;
687
+ continue;
688
+ }
689
+ // TypeScript/JavaScript: rest parameter — required_parameter containing rest_pattern
690
+ if (param.type === 'required_parameter' || param.type === 'optional_parameter') {
691
+ for (const child of param.children) {
692
+ if (child.type === 'rest_pattern') {
693
+ isVariadic = true;
694
+ break;
695
+ }
696
+ }
697
+ if (isVariadic)
698
+ continue;
699
+ }
700
+ // Kotlin: vararg modifier on a regular parameter
701
+ if (param.type === 'parameter' || param.type === 'formal_parameter') {
702
+ const prev = param.previousSibling;
703
+ if (prev?.type === 'parameter_modifiers' && prev.text.includes('vararg')) {
704
+ isVariadic = true;
705
+ }
706
+ }
707
+ parameterCount++;
708
+ }
709
+ // C/C++: bare `...` token in parameter list (not a named child — check all children)
710
+ if (!isVariadic) {
711
+ for (const child of parameterList.children) {
712
+ if (!child.isNamed && child.text === '...') {
713
+ isVariadic = true;
714
+ break;
715
+ }
716
+ }
717
+ }
718
+ }
719
+ // Return type extraction — language-specific field names
720
+ // Go: 'result' field is either a type_identifier or parameter_list (multi-return)
721
+ const goResult = node.childForFieldName?.('result');
722
+ if (goResult) {
723
+ if (goResult.type === 'parameter_list') {
724
+ // Multi-return: extract first parameter's type only (e.g. (*User, error) → *User)
725
+ const firstParam = goResult.firstNamedChild;
726
+ if (firstParam?.type === 'parameter_declaration') {
727
+ const typeNode = firstParam.childForFieldName('type');
728
+ if (typeNode)
729
+ returnType = typeNode.text;
730
+ }
731
+ else if (firstParam) {
732
+ // Unnamed return types: (string, error) — first child is a bare type node
733
+ returnType = firstParam.text;
734
+ }
735
+ }
736
+ else {
737
+ returnType = goResult.text;
738
+ }
739
+ }
740
+ // Rust: 'return_type' field — the value IS the type node (e.g. primitive_type, type_identifier).
741
+ // Skip if the node is a type_annotation (TS/Python), which is handled by the generic loop below.
742
+ if (!returnType) {
743
+ const rustReturn = node.childForFieldName?.('return_type');
744
+ if (rustReturn && rustReturn.type !== 'type_annotation') {
745
+ returnType = rustReturn.text;
746
+ }
747
+ }
748
+ // C/C++: 'type' field on function_definition
749
+ if (!returnType) {
750
+ const cppType = node.childForFieldName?.('type');
751
+ if (cppType && cppType.text !== 'void') {
752
+ returnType = cppType.text;
753
+ }
754
+ }
755
+ // C#: 'returns' field on method_declaration
756
+ if (!returnType) {
757
+ const csReturn = node.childForFieldName?.('returns');
758
+ if (csReturn && csReturn.text !== 'void') {
759
+ returnType = csReturn.text;
760
+ }
761
+ }
762
+ // TS/Rust/Python/C#/Kotlin: type_annotation or return_type child
763
+ if (!returnType) {
764
+ for (const child of node.children) {
765
+ if (child.type === 'type_annotation' || child.type === 'return_type') {
766
+ const typeNode = child.children.find((c) => c.isNamed);
767
+ if (typeNode)
768
+ returnType = typeNode.text;
769
+ }
770
+ }
771
+ }
772
+ // Kotlin: fun getUser(): User — return type is a bare user_type child of
773
+ // function_declaration. The Kotlin grammar does NOT wrap it in type_annotation
774
+ // or return_type; it appears as a direct child after function_value_parameters.
775
+ // Note: Kotlin uses function_value_parameters (not a field), so we find it by type.
776
+ if (!returnType) {
777
+ let paramsEnd = -1;
778
+ for (let i = 0; i < node.childCount; i++) {
779
+ const child = node.child(i);
780
+ if (!child)
781
+ continue;
782
+ if (child.type === 'function_value_parameters' || child.type === 'value_parameters') {
783
+ paramsEnd = child.endIndex;
784
+ }
785
+ if (paramsEnd >= 0 && child.type === 'user_type' && child.startIndex > paramsEnd) {
786
+ returnType = child.text;
787
+ break;
788
+ }
789
+ }
790
+ }
791
+ if (isVariadic)
792
+ parameterCount = undefined;
793
+ return { parameterCount, returnType };
794
+ };
795
+ /**
796
+ * Count direct arguments for a call expression across common tree-sitter grammars.
797
+ * Returns undefined when the argument container cannot be located cheaply.
798
+ */
799
+ export const countCallArguments = (callNode) => {
800
+ if (!callNode)
801
+ return undefined;
802
+ // Direct field or direct child (most languages)
803
+ let argsNode = callNode.childForFieldName('arguments')
804
+ ?? callNode.children.find((child) => CALL_ARGUMENT_LIST_TYPES.has(child.type));
805
+ // Kotlin/Swift: call_expression → call_suffix → value_arguments
806
+ // Search one level deeper for languages that wrap arguments in a suffix node
807
+ if (!argsNode) {
808
+ for (const child of callNode.children) {
809
+ if (!child.isNamed)
810
+ continue;
811
+ const nested = child.children.find((gc) => CALL_ARGUMENT_LIST_TYPES.has(gc.type));
812
+ if (nested) {
813
+ argsNode = nested;
814
+ break;
815
+ }
816
+ }
817
+ }
818
+ if (!argsNode)
819
+ return undefined;
820
+ let count = 0;
821
+ for (const child of argsNode.children) {
822
+ if (!child.isNamed)
823
+ continue;
824
+ if (child.type === 'comment')
825
+ continue;
826
+ count++;
827
+ }
828
+ return count;
829
+ };
830
+ // ── Call-form discrimination (Phase 1, Step D) ─────────────────────────
831
+ /**
832
+ * AST node types that indicate a member-access wrapper around the callee name.
833
+ * When nameNode.parent.type is one of these, the call is a member call.
834
+ */
835
+ const MEMBER_ACCESS_NODE_TYPES = new Set([
836
+ 'member_expression', // TS/JS: obj.method()
837
+ 'attribute', // Python: obj.method()
838
+ 'member_access_expression', // C#: obj.Method()
839
+ 'field_expression', // Rust/C++: obj.method() / ptr->method()
840
+ 'selector_expression', // Go: obj.Method()
841
+ 'navigation_suffix', // Kotlin/Swift: obj.method() — nameNode sits inside navigation_suffix
842
+ 'member_binding_expression', // C#: user?.Method() — null-conditional access
843
+ ]);
844
+ /**
845
+ * Call node types that are inherently constructor invocations.
846
+ * Only includes patterns that the tree-sitter queries already capture as @call.
847
+ */
848
+ const CONSTRUCTOR_CALL_NODE_TYPES = new Set([
849
+ 'constructor_invocation', // Kotlin: Foo()
850
+ 'new_expression', // TS/JS/C++: new Foo()
851
+ 'object_creation_expression', // Java/C#/PHP: new Foo()
852
+ 'implicit_object_creation_expression', // C# 9: User u = new(...)
853
+ 'composite_literal', // Go: User{...}
854
+ 'struct_expression', // Rust: User { ... }
855
+ ]);
856
+ /**
857
+ * AST node types for scoped/qualified calls (e.g., Foo::new() in Rust, Foo::bar() in C++).
858
+ */
859
+ const SCOPED_CALL_NODE_TYPES = new Set([
860
+ 'scoped_identifier', // Rust: Foo::new()
861
+ 'qualified_identifier', // C++: ns::func()
862
+ ]);
863
+ /**
864
+ * Infer whether a captured call site is a free call, member call, or constructor.
865
+ * Returns undefined if the form cannot be determined.
866
+ *
867
+ * Works by inspecting the AST structure between callNode (@call) and nameNode (@call.name).
868
+ * No tree-sitter query changes needed — the distinction is in the node types.
869
+ */
870
+ export const inferCallForm = (callNode, nameNode) => {
871
+ // 1. Constructor: callNode itself is a constructor invocation (Kotlin)
872
+ if (CONSTRUCTOR_CALL_NODE_TYPES.has(callNode.type)) {
873
+ return 'constructor';
874
+ }
875
+ // 2. Member call: nameNode's parent is a member-access wrapper
876
+ const nameParent = nameNode.parent;
877
+ if (nameParent && MEMBER_ACCESS_NODE_TYPES.has(nameParent.type)) {
878
+ return 'member';
879
+ }
880
+ // 3. PHP: the callNode itself distinguishes member vs free calls
881
+ if (callNode.type === 'member_call_expression' || callNode.type === 'nullsafe_member_call_expression') {
882
+ return 'member';
883
+ }
884
+ if (callNode.type === 'scoped_call_expression') {
885
+ return 'member'; // static call Foo::bar()
886
+ }
887
+ // 4. Java method_invocation: member if it has an 'object' field
888
+ if (callNode.type === 'method_invocation' && callNode.childForFieldName('object')) {
889
+ return 'member';
890
+ }
891
+ // 4b. Ruby call with receiver: obj.method
892
+ if (callNode.type === 'call' && callNode.childForFieldName('receiver')) {
893
+ return 'member';
894
+ }
895
+ // 5. Scoped calls (Rust Foo::new(), C++ ns::func()): treat as free
896
+ // The receiver is a type, not an instance — handled differently in Phase 3
897
+ if (nameParent && SCOPED_CALL_NODE_TYPES.has(nameParent.type)) {
898
+ return 'free';
899
+ }
900
+ // 6. Default: if nameNode is a direct child of callNode, it's a free call
901
+ if (nameNode.parent === callNode || nameParent?.parent === callNode) {
902
+ return 'free';
903
+ }
904
+ return undefined;
905
+ };
906
+ /**
907
+ * Extract the receiver identifier for member calls.
908
+ * Only captures simple identifiers — returns undefined for complex expressions
909
+ * like getUser().save() or arr[0].method().
910
+ */
911
+ const SIMPLE_RECEIVER_TYPES = new Set([
912
+ 'identifier',
913
+ 'simple_identifier',
914
+ 'variable_name', // PHP $variable (tree-sitter-php)
915
+ 'name', // PHP name node
916
+ 'this', // TS/JS/Java/C# this.method()
917
+ 'self', // Rust/Python self.method()
918
+ 'super', // TS/JS/Java/Kotlin/Ruby super.method()
919
+ 'super_expression', // Kotlin wraps super in super_expression
920
+ 'base', // C# base.Method()
921
+ 'parent', // PHP parent::method()
922
+ 'constant', // Ruby CONSTANT.method() (uppercase identifiers)
923
+ ]);
924
+ export const extractReceiverName = (nameNode) => {
925
+ const parent = nameNode.parent;
926
+ if (!parent)
927
+ return undefined;
928
+ // PHP: member_call_expression / nullsafe_member_call_expression — receiver is on the callNode
929
+ // Java: method_invocation — receiver is the 'object' field on callNode
930
+ // For these, parent of nameNode is the call itself, so check the call's object field
931
+ const callNode = parent.parent ?? parent;
932
+ let receiver = null;
933
+ // Try standard field names used across grammars
934
+ receiver = parent.childForFieldName('object') // TS/JS member_expression, Python attribute, PHP, Java
935
+ ?? parent.childForFieldName('value') // Rust field_expression
936
+ ?? parent.childForFieldName('operand') // Go selector_expression
937
+ ?? parent.childForFieldName('expression') // C# member_access_expression
938
+ ?? parent.childForFieldName('argument'); // C++ field_expression
939
+ // Java method_invocation: 'object' field is on the callNode, not on nameNode's parent
940
+ if (!receiver && callNode.type === 'method_invocation') {
941
+ receiver = callNode.childForFieldName('object');
942
+ }
943
+ // PHP: member_call_expression has 'object' on the call node
944
+ if (!receiver && (callNode.type === 'member_call_expression' || callNode.type === 'nullsafe_member_call_expression')) {
945
+ receiver = callNode.childForFieldName('object');
946
+ }
947
+ // Ruby: call node has 'receiver' field
948
+ if (!receiver && parent.type === 'call') {
949
+ receiver = parent.childForFieldName('receiver');
950
+ }
951
+ // PHP scoped_call_expression (parent::method(), self::method()):
952
+ // nameNode's direct parent IS the scoped_call_expression (name is a direct child)
953
+ if (!receiver && (parent.type === 'scoped_call_expression' || callNode.type === 'scoped_call_expression')) {
954
+ const scopedCall = parent.type === 'scoped_call_expression' ? parent : callNode;
955
+ receiver = scopedCall.childForFieldName('scope');
956
+ // relative_scope wraps 'parent'/'self'/'static' — unwrap to get the keyword
957
+ if (receiver?.type === 'relative_scope') {
958
+ receiver = receiver.firstChild;
959
+ }
960
+ }
961
+ // C# null-conditional: user?.Save() → conditional_access_expression wraps member_binding_expression
962
+ if (!receiver && parent.type === 'member_binding_expression') {
963
+ const condAccess = parent.parent;
964
+ if (condAccess?.type === 'conditional_access_expression') {
965
+ receiver = condAccess.firstNamedChild;
966
+ }
967
+ }
968
+ // Kotlin/Swift: navigation_expression target is the first child
969
+ if (!receiver && parent.type === 'navigation_suffix') {
970
+ const navExpr = parent.parent;
971
+ if (navExpr?.type === 'navigation_expression') {
972
+ // First named child is the target (receiver)
973
+ for (const child of navExpr.children) {
974
+ if (child.isNamed && child !== parent) {
975
+ receiver = child;
976
+ break;
977
+ }
978
+ }
979
+ }
980
+ }
981
+ if (!receiver)
982
+ return undefined;
983
+ // Only capture simple identifiers — refuse complex expressions
984
+ if (SIMPLE_RECEIVER_TYPES.has(receiver.type)) {
985
+ return receiver.text;
986
+ }
987
+ // Python super().method(): receiver is a call node `super()` — extract the function name
988
+ if (receiver.type === 'call') {
989
+ const func = receiver.childForFieldName('function');
990
+ if (func?.text === 'super')
991
+ return 'super';
992
+ }
993
+ return undefined;
994
+ };
995
+ /**
996
+ * Extract the raw receiver AST node for a member call.
997
+ * Unlike extractReceiverName, this returns the receiver node regardless of its type —
998
+ * including call_expression / method_invocation nodes that appear in chained calls
999
+ * like `svc.getUser().save()`.
1000
+ *
1001
+ * Returns undefined when the call is not a member call or when no receiver node
1002
+ * can be found (e.g. top-level free calls).
1003
+ */
1004
+ export const extractReceiverNode = (nameNode) => {
1005
+ const parent = nameNode.parent;
1006
+ if (!parent)
1007
+ return undefined;
1008
+ const callNode = parent.parent ?? parent;
1009
+ let receiver = null;
1010
+ receiver = parent.childForFieldName('object')
1011
+ ?? parent.childForFieldName('value')
1012
+ ?? parent.childForFieldName('operand')
1013
+ ?? parent.childForFieldName('expression')
1014
+ ?? parent.childForFieldName('argument');
1015
+ if (!receiver && callNode.type === 'method_invocation') {
1016
+ receiver = callNode.childForFieldName('object');
1017
+ }
1018
+ if (!receiver && (callNode.type === 'member_call_expression' || callNode.type === 'nullsafe_member_call_expression')) {
1019
+ receiver = callNode.childForFieldName('object');
1020
+ }
1021
+ if (!receiver && parent.type === 'call') {
1022
+ receiver = parent.childForFieldName('receiver');
1023
+ }
1024
+ if (!receiver && (parent.type === 'scoped_call_expression' || callNode.type === 'scoped_call_expression')) {
1025
+ const scopedCall = parent.type === 'scoped_call_expression' ? parent : callNode;
1026
+ receiver = scopedCall.childForFieldName('scope');
1027
+ if (receiver?.type === 'relative_scope') {
1028
+ receiver = receiver.firstChild;
1029
+ }
1030
+ }
1031
+ if (!receiver && parent.type === 'member_binding_expression') {
1032
+ const condAccess = parent.parent;
1033
+ if (condAccess?.type === 'conditional_access_expression') {
1034
+ receiver = condAccess.firstNamedChild;
1035
+ }
1036
+ }
1037
+ if (!receiver && parent.type === 'navigation_suffix') {
1038
+ const navExpr = parent.parent;
1039
+ if (navExpr?.type === 'navigation_expression') {
1040
+ for (const child of navExpr.children) {
1041
+ if (child.isNamed && child !== parent) {
1042
+ receiver = child;
1043
+ break;
1044
+ }
1045
+ }
1046
+ }
1047
+ }
1048
+ return receiver ?? undefined;
1049
+ };
1050
+ export const isVerboseIngestionEnabled = () => {
1051
+ const raw = process.env.GITNEXUS_VERBOSE;
1052
+ if (!raw)
1053
+ return false;
1054
+ const value = raw.toLowerCase();
1055
+ return value === '1' || value === 'true' || value === 'yes';
1056
+ };
1057
+ // ── Chained-call extraction ───────────────────────────────────────────────
1058
+ /** Node types representing call expressions across supported languages. */
1059
+ export const CALL_EXPRESSION_TYPES = new Set([
1060
+ 'call_expression', // TS/JS/C/C++/Go/Rust
1061
+ 'method_invocation', // Java
1062
+ 'member_call_expression', // PHP
1063
+ 'nullsafe_member_call_expression', // PHP ?.
1064
+ 'call', // Python/Ruby
1065
+ 'invocation_expression', // C#
1066
+ ]);
1067
+ /**
1068
+ * Hard limit on chain depth to prevent runaway recursion.
1069
+ * For `a.b().c().d()`, the chain has depth 2 (b and c before d).
1070
+ */
1071
+ export const MAX_CHAIN_DEPTH = 3;
1072
+ /**
1073
+ * Walk a receiver AST node that is itself a call expression, accumulating the
1074
+ * chain of intermediate method names up to MAX_CHAIN_DEPTH.
1075
+ *
1076
+ * For `svc.getUser().save()`, called with the receiver of `save` (getUser() call):
1077
+ * returns { chain: ['getUser'], baseReceiverName: 'svc' }
1078
+ *
1079
+ * For `a.b().c().d()`, called with the receiver of `d` (c() call):
1080
+ * returns { chain: ['b', 'c'], baseReceiverName: 'a' }
1081
+ */
1082
+ export function extractCallChain(receiverCallNode) {
1083
+ const chain = [];
1084
+ let current = receiverCallNode;
1085
+ while (CALL_EXPRESSION_TYPES.has(current.type) && chain.length < MAX_CHAIN_DEPTH) {
1086
+ // Extract the method name from this call node.
1087
+ const funcNode = current.childForFieldName?.('function')
1088
+ ?? current.childForFieldName?.('name')
1089
+ ?? current.childForFieldName?.('method'); // Ruby `call` node
1090
+ let methodName;
1091
+ let innerReceiver = null;
1092
+ if (funcNode) {
1093
+ // member_expression / attribute: last named child is the method identifier
1094
+ methodName = funcNode.lastNamedChild?.text ?? funcNode.text;
1095
+ }
1096
+ // Kotlin/Swift: call_expression exposes callee as firstNamedChild, not a field.
1097
+ // navigation_expression: method name is in navigation_suffix → simple_identifier.
1098
+ if (!funcNode && current.type === 'call_expression') {
1099
+ const callee = current.firstNamedChild;
1100
+ if (callee?.type === 'navigation_expression') {
1101
+ const suffix = callee.lastNamedChild;
1102
+ if (suffix?.type === 'navigation_suffix') {
1103
+ methodName = suffix.lastNamedChild?.text;
1104
+ // The receiver is the part of navigation_expression before the suffix
1105
+ for (let i = 0; i < callee.namedChildCount; i++) {
1106
+ const child = callee.namedChild(i);
1107
+ if (child && child.type !== 'navigation_suffix') {
1108
+ innerReceiver = child;
1109
+ break;
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+ if (!methodName)
1116
+ break;
1117
+ chain.unshift(methodName); // build chain outermost-last
1118
+ // Walk into the receiver of this call to continue the chain
1119
+ if (!innerReceiver && funcNode) {
1120
+ innerReceiver = funcNode.childForFieldName?.('object')
1121
+ ?? funcNode.childForFieldName?.('value')
1122
+ ?? funcNode.childForFieldName?.('operand')
1123
+ ?? funcNode.childForFieldName?.('expression');
1124
+ }
1125
+ // Java method_invocation: object field is on the call node
1126
+ if (!innerReceiver && current.type === 'method_invocation') {
1127
+ innerReceiver = current.childForFieldName?.('object');
1128
+ }
1129
+ // PHP member_call_expression
1130
+ if (!innerReceiver && (current.type === 'member_call_expression' || current.type === 'nullsafe_member_call_expression')) {
1131
+ innerReceiver = current.childForFieldName?.('object');
1132
+ }
1133
+ // Ruby `call` node: receiver field is on the call node itself
1134
+ if (!innerReceiver && current.type === 'call') {
1135
+ innerReceiver = current.childForFieldName?.('receiver');
1136
+ }
1137
+ if (!innerReceiver)
1138
+ break;
1139
+ if (CALL_EXPRESSION_TYPES.has(innerReceiver.type)) {
1140
+ current = innerReceiver; // continue walking
1141
+ }
1142
+ else {
1143
+ // Reached a simple identifier — the base receiver
1144
+ return { chain, baseReceiverName: innerReceiver.text || undefined };
1145
+ }
1146
+ }
1147
+ return chain.length > 0 ? { chain, baseReceiverName: undefined } : undefined;
1148
+ }
1149
+ /** Node types representing member/field access across languages. */
1150
+ const FIELD_ACCESS_NODE_TYPES = new Set([
1151
+ 'member_expression', // TS/JS
1152
+ 'member_access_expression', // C#
1153
+ 'selector_expression', // Go
1154
+ 'field_expression', // Rust/C++
1155
+ 'field_access', // Java
1156
+ 'attribute', // Python
1157
+ 'navigation_expression', // Kotlin/Swift
1158
+ 'member_binding_expression', // C# null-conditional (user?.Address)
1159
+ ]);
1160
+ /**
1161
+ * Walk a receiver AST node that may interleave field accesses and method calls,
1162
+ * building a unified chain of steps up to MAX_CHAIN_DEPTH.
1163
+ *
1164
+ * For `svc.getUser().address.save()`, called with the receiver of `save`
1165
+ * (`svc.getUser().address`, a field access node):
1166
+ * returns { chain: [{ kind:'call', name:'getUser' }, { kind:'field', name:'address' }],
1167
+ * baseReceiverName: 'svc' }
1168
+ *
1169
+ * For `user.getAddress().city.getName()`, called with receiver of `getName`
1170
+ * (`user.getAddress().city`):
1171
+ * returns { chain: [{ kind:'call', name:'getAddress' }, { kind:'field', name:'city' }],
1172
+ * baseReceiverName: 'user' }
1173
+ *
1174
+ * Pure field chains and pure call chains are special cases (all steps same kind).
1175
+ */
1176
+ export function extractMixedChain(receiverNode) {
1177
+ const chain = [];
1178
+ let current = receiverNode;
1179
+ while (chain.length < MAX_CHAIN_DEPTH) {
1180
+ if (CALL_EXPRESSION_TYPES.has(current.type)) {
1181
+ // ── Call expression: extract method name + inner receiver ────────────
1182
+ const funcNode = current.childForFieldName?.('function')
1183
+ ?? current.childForFieldName?.('name')
1184
+ ?? current.childForFieldName?.('method');
1185
+ let methodName;
1186
+ let innerReceiver = null;
1187
+ if (funcNode) {
1188
+ methodName = funcNode.lastNamedChild?.text ?? funcNode.text;
1189
+ }
1190
+ // Kotlin/Swift: call_expression → navigation_expression
1191
+ if (!funcNode && current.type === 'call_expression') {
1192
+ const callee = current.firstNamedChild;
1193
+ if (callee?.type === 'navigation_expression') {
1194
+ const suffix = callee.lastNamedChild;
1195
+ if (suffix?.type === 'navigation_suffix') {
1196
+ methodName = suffix.lastNamedChild?.text;
1197
+ for (let i = 0; i < callee.namedChildCount; i++) {
1198
+ const child = callee.namedChild(i);
1199
+ if (child && child.type !== 'navigation_suffix') {
1200
+ innerReceiver = child;
1201
+ break;
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+ if (!methodName)
1208
+ break;
1209
+ chain.unshift({ kind: 'call', name: methodName });
1210
+ if (!innerReceiver && funcNode) {
1211
+ innerReceiver = funcNode.childForFieldName?.('object')
1212
+ ?? funcNode.childForFieldName?.('value')
1213
+ ?? funcNode.childForFieldName?.('operand')
1214
+ ?? funcNode.childForFieldName?.('argument') // C/C++ field_expression
1215
+ ?? funcNode.childForFieldName?.('expression')
1216
+ ?? null;
1217
+ }
1218
+ if (!innerReceiver && current.type === 'method_invocation') {
1219
+ innerReceiver = current.childForFieldName?.('object') ?? null;
1220
+ }
1221
+ if (!innerReceiver && (current.type === 'member_call_expression' || current.type === 'nullsafe_member_call_expression')) {
1222
+ innerReceiver = current.childForFieldName?.('object') ?? null;
1223
+ }
1224
+ if (!innerReceiver && current.type === 'call') {
1225
+ innerReceiver = current.childForFieldName?.('receiver') ?? null;
1226
+ }
1227
+ if (!innerReceiver)
1228
+ break;
1229
+ if (CALL_EXPRESSION_TYPES.has(innerReceiver.type) || FIELD_ACCESS_NODE_TYPES.has(innerReceiver.type)) {
1230
+ current = innerReceiver;
1231
+ }
1232
+ else {
1233
+ return { chain, baseReceiverName: innerReceiver.text || undefined };
1234
+ }
1235
+ }
1236
+ else if (FIELD_ACCESS_NODE_TYPES.has(current.type)) {
1237
+ // ── Field/member access: extract property name + inner object ─────────
1238
+ let propertyName;
1239
+ let innerObject = null;
1240
+ if (current.type === 'navigation_expression') {
1241
+ for (const child of current.children ?? []) {
1242
+ if (child.type === 'navigation_suffix') {
1243
+ for (const sc of child.children ?? []) {
1244
+ if (sc.isNamed && sc.type !== '.') {
1245
+ propertyName = sc.text;
1246
+ break;
1247
+ }
1248
+ }
1249
+ }
1250
+ else if (child.isNamed && !innerObject) {
1251
+ innerObject = child;
1252
+ }
1253
+ }
1254
+ }
1255
+ else if (current.type === 'attribute') {
1256
+ innerObject = current.childForFieldName?.('object') ?? null;
1257
+ propertyName = current.childForFieldName?.('attribute')?.text;
1258
+ }
1259
+ else {
1260
+ innerObject = current.childForFieldName?.('object')
1261
+ ?? current.childForFieldName?.('value')
1262
+ ?? current.childForFieldName?.('operand')
1263
+ ?? current.childForFieldName?.('argument') // C/C++ field_expression
1264
+ ?? current.childForFieldName?.('expression')
1265
+ ?? null;
1266
+ propertyName = (current.childForFieldName?.('property')
1267
+ ?? current.childForFieldName?.('field')
1268
+ ?? current.childForFieldName?.('name'))?.text;
1269
+ }
1270
+ if (!propertyName)
1271
+ break;
1272
+ chain.unshift({ kind: 'field', name: propertyName });
1273
+ if (!innerObject)
1274
+ break;
1275
+ if (CALL_EXPRESSION_TYPES.has(innerObject.type) || FIELD_ACCESS_NODE_TYPES.has(innerObject.type)) {
1276
+ current = innerObject;
1277
+ }
1278
+ else {
1279
+ return { chain, baseReceiverName: innerObject.text || undefined };
1280
+ }
1281
+ }
1282
+ else {
1283
+ // Simple identifier — this is the base receiver
1284
+ return chain.length > 0
1285
+ ? { chain, baseReceiverName: current.text || undefined }
1286
+ : undefined;
1287
+ }
1288
+ }
1289
+ return chain.length > 0 ? { chain, baseReceiverName: undefined } : undefined;
1290
+ }