gitnexus 1.5.2 → 1.6.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 (207) hide show
  1. package/README.md +10 -0
  2. package/dist/_shared/graph/types.d.ts +1 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +1 -0
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/language-detection.d.ts.map +1 -1
  7. package/dist/_shared/language-detection.js +2 -0
  8. package/dist/_shared/language-detection.js.map +1 -1
  9. package/dist/_shared/languages.d.ts +1 -0
  10. package/dist/_shared/languages.d.ts.map +1 -1
  11. package/dist/_shared/languages.js +1 -0
  12. package/dist/_shared/languages.js.map +1 -1
  13. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  14. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.js +3 -1
  16. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  17. package/dist/_shared/mro-strategy.d.ts +19 -0
  18. package/dist/_shared/mro-strategy.d.ts.map +1 -0
  19. package/dist/_shared/mro-strategy.js +2 -0
  20. package/dist/_shared/mro-strategy.js.map +1 -0
  21. package/dist/cli/ai-context.d.ts +1 -0
  22. package/dist/cli/ai-context.js +28 -4
  23. package/dist/cli/analyze.d.ts +2 -0
  24. package/dist/cli/analyze.js +2 -1
  25. package/dist/cli/group.d.ts +2 -0
  26. package/dist/cli/group.js +233 -0
  27. package/dist/cli/index.js +3 -0
  28. package/dist/cli/serve.js +4 -1
  29. package/dist/cli/setup.js +34 -3
  30. package/dist/cli/wiki.js +15 -44
  31. package/dist/config/ignore-service.js +8 -3
  32. package/dist/core/augmentation/engine.js +1 -1
  33. package/dist/core/git-staleness.d.ts +13 -0
  34. package/dist/core/git-staleness.js +29 -0
  35. package/dist/core/group/bridge-db.d.ts +82 -0
  36. package/dist/core/group/bridge-db.js +460 -0
  37. package/dist/core/group/bridge-schema.d.ts +27 -0
  38. package/dist/core/group/bridge-schema.js +55 -0
  39. package/dist/core/group/config-parser.d.ts +3 -0
  40. package/dist/core/group/config-parser.js +83 -0
  41. package/dist/core/group/contract-extractor.d.ts +7 -0
  42. package/dist/core/group/contract-extractor.js +1 -0
  43. package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
  44. package/dist/core/group/extractors/grpc-extractor.js +264 -0
  45. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  46. package/dist/core/group/extractors/http-route-extractor.js +428 -0
  47. package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
  48. package/dist/core/group/extractors/topic-extractor.js +234 -0
  49. package/dist/core/group/matching.d.ts +13 -0
  50. package/dist/core/group/matching.js +198 -0
  51. package/dist/core/group/normalization.d.ts +3 -0
  52. package/dist/core/group/normalization.js +115 -0
  53. package/dist/core/group/service-boundary-detector.d.ts +8 -0
  54. package/dist/core/group/service-boundary-detector.js +155 -0
  55. package/dist/core/group/service.d.ts +46 -0
  56. package/dist/core/group/service.js +160 -0
  57. package/dist/core/group/storage.d.ts +9 -0
  58. package/dist/core/group/storage.js +91 -0
  59. package/dist/core/group/sync.d.ts +21 -0
  60. package/dist/core/group/sync.js +148 -0
  61. package/dist/core/group/types.d.ts +130 -0
  62. package/dist/core/group/types.js +1 -0
  63. package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
  64. package/dist/core/ingestion/binding-accumulator.js +332 -0
  65. package/dist/core/ingestion/call-processor.d.ts +155 -24
  66. package/dist/core/ingestion/call-processor.js +1129 -247
  67. package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
  68. package/dist/core/ingestion/class-extractors/generic.js +135 -0
  69. package/dist/core/ingestion/class-types.d.ts +34 -0
  70. package/dist/core/ingestion/class-types.js +1 -0
  71. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
  72. package/dist/core/ingestion/entry-point-scoring.js +1 -0
  73. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -1
  74. package/dist/core/ingestion/field-extractors/configs/helpers.js +13 -3
  75. package/dist/core/ingestion/field-types.d.ts +2 -2
  76. package/dist/core/ingestion/filesystem-walker.js +8 -0
  77. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  78. package/dist/core/ingestion/framework-detection.js +1 -0
  79. package/dist/core/ingestion/heritage-processor.d.ts +8 -15
  80. package/dist/core/ingestion/heritage-processor.js +15 -28
  81. package/dist/core/ingestion/import-processor.d.ts +1 -11
  82. package/dist/core/ingestion/import-processor.js +0 -12
  83. package/dist/core/ingestion/import-resolvers/utils.js +1 -0
  84. package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
  85. package/dist/core/ingestion/import-resolvers/vue.js +9 -0
  86. package/dist/core/ingestion/language-provider.d.ts +6 -3
  87. package/dist/core/ingestion/languages/c-cpp.js +168 -1
  88. package/dist/core/ingestion/languages/csharp.js +20 -0
  89. package/dist/core/ingestion/languages/dart.js +26 -4
  90. package/dist/core/ingestion/languages/go.js +22 -0
  91. package/dist/core/ingestion/languages/index.d.ts +1 -0
  92. package/dist/core/ingestion/languages/index.js +2 -0
  93. package/dist/core/ingestion/languages/java.js +17 -0
  94. package/dist/core/ingestion/languages/kotlin.js +24 -1
  95. package/dist/core/ingestion/languages/php.js +23 -11
  96. package/dist/core/ingestion/languages/python.js +9 -0
  97. package/dist/core/ingestion/languages/ruby.js +28 -0
  98. package/dist/core/ingestion/languages/rust.js +38 -0
  99. package/dist/core/ingestion/languages/swift.js +31 -0
  100. package/dist/core/ingestion/languages/typescript.d.ts +1 -0
  101. package/dist/core/ingestion/languages/typescript.js +54 -1
  102. package/dist/core/ingestion/languages/vue.d.ts +13 -0
  103. package/dist/core/ingestion/languages/vue.js +81 -0
  104. package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
  105. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
  106. package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
  107. package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
  108. package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
  109. package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
  110. package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
  111. package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
  112. package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
  113. package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
  114. package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
  115. package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
  116. package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
  117. package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
  118. package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
  119. package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
  120. package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
  121. package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
  122. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.d.ts +3 -0
  123. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +338 -0
  124. package/dist/core/ingestion/method-extractors/generic.js +38 -15
  125. package/dist/core/ingestion/method-types.d.ts +25 -0
  126. package/dist/core/ingestion/model/field-registry.d.ts +18 -0
  127. package/dist/core/ingestion/model/field-registry.js +22 -0
  128. package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
  129. package/dist/core/ingestion/model/heritage-map.js +159 -0
  130. package/dist/core/ingestion/model/index.d.ts +20 -0
  131. package/dist/core/ingestion/model/index.js +41 -0
  132. package/dist/core/ingestion/model/method-registry.d.ts +62 -0
  133. package/dist/core/ingestion/model/method-registry.js +130 -0
  134. package/dist/core/ingestion/model/registration-table.d.ts +139 -0
  135. package/dist/core/ingestion/model/registration-table.js +224 -0
  136. package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
  137. package/dist/core/ingestion/model/resolution-context.js +337 -0
  138. package/dist/core/ingestion/model/resolve.d.ts +56 -0
  139. package/dist/core/ingestion/model/resolve.js +242 -0
  140. package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
  141. package/dist/core/ingestion/model/semantic-model.js +120 -0
  142. package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
  143. package/dist/core/ingestion/model/symbol-table.js +206 -0
  144. package/dist/core/ingestion/model/type-registry.d.ts +39 -0
  145. package/dist/core/ingestion/model/type-registry.js +62 -0
  146. package/dist/core/ingestion/mro-processor.d.ts +4 -3
  147. package/dist/core/ingestion/mro-processor.js +310 -106
  148. package/dist/core/ingestion/parsing-processor.d.ts +5 -4
  149. package/dist/core/ingestion/parsing-processor.js +210 -85
  150. package/dist/core/ingestion/pipeline.d.ts +2 -0
  151. package/dist/core/ingestion/pipeline.js +192 -68
  152. package/dist/core/ingestion/tree-sitter-queries.d.ts +6 -6
  153. package/dist/core/ingestion/tree-sitter-queries.js +37 -0
  154. package/dist/core/ingestion/type-env.d.ts +15 -2
  155. package/dist/core/ingestion/type-env.js +163 -102
  156. package/dist/core/ingestion/type-extractors/csharp.js +17 -0
  157. package/dist/core/ingestion/type-extractors/jvm.js +11 -0
  158. package/dist/core/ingestion/type-extractors/php.js +0 -55
  159. package/dist/core/ingestion/type-extractors/ruby.js +0 -32
  160. package/dist/core/ingestion/type-extractors/swift.js +13 -0
  161. package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
  162. package/dist/core/ingestion/type-extractors/typescript.js +66 -69
  163. package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
  164. package/dist/core/ingestion/utils/ast-helpers.js +129 -565
  165. package/dist/core/ingestion/utils/method-props.d.ts +32 -0
  166. package/dist/core/ingestion/utils/method-props.js +147 -0
  167. package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
  168. package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
  169. package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
  170. package/dist/core/ingestion/workers/parse-worker.js +463 -198
  171. package/dist/core/lbug/lbug-adapter.d.ts +6 -0
  172. package/dist/core/lbug/lbug-adapter.js +68 -3
  173. package/dist/core/lbug/pool-adapter.d.ts +76 -0
  174. package/dist/core/lbug/pool-adapter.js +522 -0
  175. package/dist/core/run-analyze.d.ts +2 -0
  176. package/dist/core/run-analyze.js +1 -1
  177. package/dist/core/search/bm25-index.js +1 -1
  178. package/dist/core/tree-sitter/parser-loader.js +1 -0
  179. package/dist/core/wiki/graph-queries.js +1 -1
  180. package/dist/core/wiki/html-viewer.js +6 -4
  181. package/dist/core/wiki/llm-client.js +4 -6
  182. package/dist/mcp/core/embedder.js +6 -5
  183. package/dist/mcp/core/lbug-adapter.d.ts +3 -63
  184. package/dist/mcp/core/lbug-adapter.js +3 -484
  185. package/dist/mcp/local/local-backend.d.ts +31 -2
  186. package/dist/mcp/local/local-backend.js +255 -46
  187. package/dist/mcp/resources.js +5 -4
  188. package/dist/mcp/staleness.d.ts +3 -13
  189. package/dist/mcp/staleness.js +2 -31
  190. package/dist/mcp/tools.js +80 -4
  191. package/dist/server/analyze-job.d.ts +2 -0
  192. package/dist/server/analyze-job.js +4 -0
  193. package/dist/server/api.d.ts +20 -1
  194. package/dist/server/api.js +306 -71
  195. package/dist/server/git-clone.d.ts +2 -1
  196. package/dist/server/git-clone.js +98 -5
  197. package/dist/storage/git.d.ts +13 -0
  198. package/dist/storage/git.js +25 -0
  199. package/dist/storage/repo-manager.js +1 -1
  200. package/package.json +8 -2
  201. package/scripts/patch-tree-sitter-swift.cjs +78 -0
  202. package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
  203. package/dist/core/ingestion/named-binding-processor.js +0 -42
  204. package/dist/core/ingestion/resolution-context.d.ts +0 -58
  205. package/dist/core/ingestion/resolution-context.js +0 -135
  206. package/dist/core/ingestion/symbol-table.d.ts +0 -79
  207. package/dist/core/ingestion/symbol-table.js +0 -115
@@ -1,5 +1,4 @@
1
1
  import { generateId } from '../../../lib/utils.js';
2
- import { extractSimpleTypeName } from '../type-extractors/shared.js';
3
2
  /**
4
3
  * Ordered list of definition capture keys for tree-sitter query matches.
5
4
  * Used to extract the definition node from a capture map.
@@ -38,7 +37,13 @@ export const getDefinitionNodeFromCaptures = (captureMap) => {
38
37
  };
39
38
  /**
40
39
  * Node types that represent function/method definitions across languages.
41
- * Used to find the enclosing function for a call site.
40
+ * Used by parent-walk in call-processor, parse-worker, and type-env to detect
41
+ * enclosing function scope boundaries.
42
+ *
43
+ * INVARIANT: This set MUST be a superset of every language's
44
+ * MethodExtractionConfig.methodNodeTypes. When adding a new node type to a
45
+ * MethodExtractor config, add it here too — otherwise enclosing-function
46
+ * resolution will silently miss that node type during parent-walks.
42
47
  */
43
48
  export const FUNCTION_NODE_TYPES = new Set([
44
49
  // TypeScript/JavaScript
@@ -81,17 +86,13 @@ export const FUNCTION_NODE_TYPES = new Set([
81
86
  'method_signature',
82
87
  ]);
83
88
  /**
84
- * Node types for standard function declarations that need C/C++ declarator handling.
85
- * Used by extractFunctionName to determine how to extract the function name.
89
+ * AST node types that represent a class-like container (for HAS_METHOD edge extraction).
90
+ *
91
+ * INVARIANT: When a language config adds a new node type to `typeDeclarationNodes`,
92
+ * that type must also be added here AND to `CONTAINER_TYPE_TO_LABEL` below,
93
+ * otherwise `findEnclosingClassNode` won't recognize it and methods may get
94
+ * orphaned HAS_METHOD edges or incorrect labels.
86
95
  */
87
- export const FUNCTION_DECLARATION_TYPES = new Set([
88
- 'function_declaration',
89
- 'function_definition',
90
- 'async_function_declaration',
91
- 'generator_function_declaration',
92
- 'function_item',
93
- ]);
94
- /** AST node types that represent a class-like container (for HAS_METHOD edge extraction) */
95
96
  export const CLASS_CONTAINER_TYPES = new Set([
96
97
  'class_declaration',
97
98
  'abstract_class_declaration',
@@ -106,10 +107,16 @@ export const CLASS_CONTAINER_TYPES = new Set([
106
107
  'enum_item',
107
108
  'class_definition',
108
109
  'trait_declaration',
110
+ // PHP
111
+ 'enum_declaration',
109
112
  'protocol_declaration',
113
+ // Dart
114
+ 'mixin_declaration',
115
+ 'extension_declaration',
110
116
  // Ruby
111
117
  'class',
112
118
  'module',
119
+ 'singleton_class', // Ruby: class << self
113
120
  // Kotlin
114
121
  'object_declaration',
115
122
  'companion_object',
@@ -127,25 +134,17 @@ export const CONTAINER_TYPE_TO_LABEL = {
127
134
  struct_item: 'Struct',
128
135
  enum_item: 'Enum',
129
136
  trait_declaration: 'Trait',
137
+ enum_declaration: 'Enum',
130
138
  record_declaration: 'Record',
131
139
  protocol_declaration: 'Interface',
140
+ mixin_declaration: 'Mixin',
141
+ extension_declaration: 'Extension',
132
142
  class: 'Class',
133
143
  module: 'Module',
144
+ singleton_class: 'Class', // Ruby: class << self inherits enclosing class name
134
145
  object_declaration: 'Class',
135
146
  companion_object: 'Class',
136
147
  };
137
- /** Check if a Kotlin function_declaration capture is inside a class_body (i.e., a method).
138
- * Kotlin grammar uses function_declaration for both top-level functions and class methods.
139
- * Returns true when the captured definition node has a class_body ancestor. */
140
- export function isKotlinClassMethod(captureNode) {
141
- let ancestor = captureNode?.parent;
142
- while (ancestor) {
143
- if (ancestor.type === 'class_body')
144
- return true;
145
- ancestor = ancestor.parent;
146
- }
147
- return false;
148
- }
149
148
  /**
150
149
  * Determine the graph node label from a tree-sitter capture map.
151
150
  * Handles language-specific reclassification via the provider's labelOverride hook
@@ -209,31 +208,31 @@ export function getLabelFromCaptures(captureMap, provider) {
209
208
  return 'Template';
210
209
  return 'CodeElement';
211
210
  }
212
- /** Walk up AST to find enclosing class/struct/interface/impl, return its generateId or null.
211
+ /** Walk up AST to find enclosing class/struct/interface/impl, return its ID and name.
213
212
  * For Go method_declaration nodes, extracts receiver type (e.g. `func (u *User) Save()` → User struct). */
214
- export const findEnclosingClassId = (node, filePath) => {
213
+ export const findEnclosingClassInfo = (node, filePath) => {
215
214
  let current = node.parent;
216
215
  while (current) {
217
216
  // Go: method_declaration has a receiver parameter with the struct type
218
217
  if (current.type === 'method_declaration') {
219
218
  const receiver = current.childForFieldName?.('receiver');
220
219
  if (receiver) {
221
- // receiver is a parameter_list: (u *User) or (u User)
222
220
  const paramDecl = receiver.namedChildren?.find?.((c) => c.type === 'parameter_declaration');
223
221
  if (paramDecl) {
224
222
  const typeNode = paramDecl.childForFieldName?.('type');
225
223
  if (typeNode) {
226
- // Unwrap pointer_type (*User → User)
227
224
  const inner = typeNode.type === 'pointer_type' ? typeNode.firstNamedChild : typeNode;
228
225
  if (inner && (inner.type === 'type_identifier' || inner.type === 'identifier')) {
229
- return generateId('Struct', `${filePath}:${inner.text}`);
226
+ return {
227
+ classId: generateId('Struct', `${filePath}:${inner.text}`),
228
+ className: inner.text,
229
+ };
230
230
  }
231
231
  }
232
232
  }
233
233
  }
234
234
  }
235
235
  // Go: type_declaration wrapping a struct_type (type User struct { ... })
236
- // field_declaration → field_declaration_list → struct_type → type_spec → type_declaration
237
236
  if (current.type === 'type_declaration') {
238
237
  const typeSpec = current.children?.find((c) => c.type === 'type_spec');
239
238
  if (typeSpec) {
@@ -242,25 +241,60 @@ export const findEnclosingClassId = (node, filePath) => {
242
241
  const nameNode = typeSpec.childForFieldName?.('name');
243
242
  if (nameNode) {
244
243
  const label = typeBody.type === 'struct_type' ? 'Struct' : 'Interface';
245
- return generateId(label, `${filePath}:${nameNode.text}`);
244
+ return {
245
+ classId: generateId(label, `${filePath}:${nameNode.text}`),
246
+ className: nameNode.text,
247
+ };
246
248
  }
247
249
  }
248
250
  }
249
251
  }
250
252
  if (CLASS_CONTAINER_TYPES.has(current.type)) {
251
253
  // Rust impl_item: for `impl Trait for Struct {}`, pick the type after `for`
254
+ // NOTE: This impl_item ownership logic is duplicated in rust.ts:extractOwnerName.
255
+ // If modifying this block, update the other location too.
252
256
  if (current.type === 'impl_item') {
253
257
  const children = current.children ?? [];
254
258
  const forIdx = children.findIndex((c) => c.text === 'for');
255
259
  if (forIdx !== -1) {
256
260
  const nameNode = children
257
261
  .slice(forIdx + 1)
258
- .find((c) => c.type === 'type_identifier' || c.type === 'identifier');
262
+ .find((c) => c.type === 'type_identifier' ||
263
+ c.type === 'scoped_type_identifier' ||
264
+ c.type === 'identifier');
259
265
  if (nameNode) {
260
- return generateId('Impl', `${filePath}:${nameNode.text}`);
266
+ return {
267
+ classId: generateId('Struct', `${filePath}:${nameNode.text}`),
268
+ className: nameNode.text,
269
+ };
261
270
  }
262
271
  }
263
- // Fall through: plain `impl Struct {}` — use first type_identifier below
272
+ const firstType = children.find((c) => c.type === 'type_identifier');
273
+ if (firstType) {
274
+ return {
275
+ classId: generateId('Impl', `${filePath}:${firstType.text}`),
276
+ className: firstType.text,
277
+ };
278
+ }
279
+ }
280
+ // Ruby singleton_class (class << self): walk up to the enclosing class/module
281
+ // to inherit its name. singleton_class has no name field — its receiver is
282
+ // `self` (node type 'self'), not 'identifier' or 'constant'.
283
+ if (current.type === 'singleton_class') {
284
+ let ancestor = current.parent;
285
+ while (ancestor) {
286
+ if (ancestor.type === 'class' || ancestor.type === 'module') {
287
+ const classNameNode = ancestor.childForFieldName?.('name');
288
+ if (classNameNode) {
289
+ return {
290
+ classId: generateId('Class', `${filePath}:${classNameNode.text}`),
291
+ className: classNameNode.text,
292
+ };
293
+ }
294
+ }
295
+ ancestor = ancestor.parent;
296
+ }
297
+ // No enclosing class/module — skip singleton_class and keep walking up
264
298
  }
265
299
  const nameNode = current.childForFieldName?.('name') ??
266
300
  current.children?.find((c) => c.type === 'type_identifier' ||
@@ -268,14 +302,29 @@ export const findEnclosingClassId = (node, filePath) => {
268
302
  c.type === 'name' ||
269
303
  c.type === 'constant');
270
304
  if (nameNode) {
271
- const label = CONTAINER_TYPE_TO_LABEL[current.type] || 'Class';
272
- return generateId(label, `${filePath}:${nameNode.text}`);
305
+ let label = CONTAINER_TYPE_TO_LABEL[current.type] || 'Class';
306
+ // Kotlin: class_declaration with an anonymous "interface" keyword child
307
+ // is actually an interface, not a class. Refine the label to match the
308
+ // node ID generated from the tree-sitter query capture (@definition.interface).
309
+ if (current.type === 'class_declaration' &&
310
+ label === 'Class' &&
311
+ current.children?.some((c) => c.type === 'interface')) {
312
+ label = 'Interface';
313
+ }
314
+ return {
315
+ classId: generateId(label, `${filePath}:${nameNode.text}`),
316
+ className: nameNode.text,
317
+ };
273
318
  }
274
319
  }
275
320
  current = current.parent;
276
321
  }
277
322
  return null;
278
323
  };
324
+ /** Convenience wrapper: returns just the class ID string (backward compat). */
325
+ export const findEnclosingClassId = (node, filePath) => {
326
+ return findEnclosingClassInfo(node, filePath)?.classId ?? null;
327
+ };
279
328
  /**
280
329
  * Find a child of `childType` within a sibling node of `siblingType`.
281
330
  * Used for Kotlin AST traversal where visibility_modifier lives inside a modifiers sibling.
@@ -293,530 +342,57 @@ export const findSiblingChild = (parent, siblingType, childType) => {
293
342
  }
294
343
  return null;
295
344
  };
296
- /**
297
- * Extract function name and label from a function_definition or similar AST node.
298
- * Handles C/C++ qualified_identifier (ClassName::MethodName) and other language patterns.
299
- */
300
- export const extractFunctionName = (node) => {
301
- let funcName = null;
302
- let label = 'Function';
303
- // Swift init/deinit
304
- if (node.type === 'init_declaration' || node.type === 'deinit_declaration') {
305
- return {
306
- funcName: node.type === 'init_declaration' ? 'init' : 'deinit',
307
- label: 'Constructor',
308
- };
309
- }
310
- if (FUNCTION_DECLARATION_TYPES.has(node.type)) {
311
- // C/C++: function_definition -> [pointer_declarator ->] function_declarator -> qualified_identifier/identifier
312
- // Unwrap pointer_declarator / reference_declarator wrappers to reach function_declarator
313
- let declarator = node.childForFieldName?.('declarator');
314
- if (!declarator) {
315
- for (let i = 0; i < node.childCount; i++) {
316
- const c = node.child(i);
317
- if (c?.type === 'function_declarator') {
318
- declarator = c;
319
- break;
320
- }
321
- }
322
- }
323
- while (declarator &&
324
- (declarator.type === 'pointer_declarator' || declarator.type === 'reference_declarator')) {
325
- let nextDeclarator = declarator.childForFieldName?.('declarator');
326
- if (!nextDeclarator) {
327
- for (let i = 0; i < declarator.childCount; i++) {
328
- const c = declarator.child(i);
329
- if (c?.type === 'function_declarator' ||
330
- c?.type === 'pointer_declarator' ||
331
- c?.type === 'reference_declarator') {
332
- nextDeclarator = c;
333
- break;
334
- }
335
- }
336
- }
337
- declarator = nextDeclarator;
338
- }
339
- if (declarator) {
340
- let innerDeclarator = declarator.childForFieldName?.('declarator');
341
- if (!innerDeclarator) {
342
- for (let i = 0; i < declarator.childCount; i++) {
343
- const c = declarator.child(i);
344
- if (c?.type === 'qualified_identifier' ||
345
- c?.type === 'identifier' ||
346
- c?.type === 'field_identifier' ||
347
- c?.type === 'parenthesized_declarator') {
348
- innerDeclarator = c;
349
- break;
350
- }
351
- }
352
- }
353
- if (innerDeclarator?.type === 'qualified_identifier') {
354
- let nameNode = innerDeclarator.childForFieldName?.('name');
355
- if (!nameNode) {
356
- for (let i = 0; i < innerDeclarator.childCount; i++) {
357
- const c = innerDeclarator.child(i);
358
- if (c?.type === 'identifier') {
359
- nameNode = c;
360
- break;
361
- }
362
- }
363
- }
364
- if (nameNode?.text) {
365
- funcName = nameNode.text;
366
- label = 'Method';
367
- }
368
- }
369
- else if (innerDeclarator?.type === 'identifier' ||
370
- innerDeclarator?.type === 'field_identifier') {
371
- // field_identifier is used for method names inside C++ class bodies
372
- funcName = innerDeclarator.text;
373
- if (innerDeclarator.type === 'field_identifier')
374
- label = 'Method';
375
- }
376
- else if (innerDeclarator?.type === 'parenthesized_declarator') {
377
- let nestedId = null;
378
- for (let i = 0; i < innerDeclarator.childCount; i++) {
379
- const c = innerDeclarator.child(i);
380
- if (c?.type === 'qualified_identifier' || c?.type === 'identifier') {
381
- nestedId = c;
382
- break;
383
- }
384
- }
385
- if (nestedId?.type === 'qualified_identifier') {
386
- let nameNode = nestedId.childForFieldName?.('name');
387
- if (!nameNode) {
388
- for (let i = 0; i < nestedId.childCount; i++) {
389
- const c = nestedId.child(i);
390
- if (c?.type === 'identifier') {
391
- nameNode = c;
392
- break;
393
- }
394
- }
395
- }
396
- if (nameNode?.text) {
397
- funcName = nameNode.text;
398
- label = 'Method';
399
- }
400
- }
401
- else if (nestedId?.type === 'identifier') {
402
- funcName = nestedId.text;
403
- }
404
- }
405
- }
406
- // Fallback for other languages (Kotlin uses simple_identifier, Swift uses simple_identifier)
407
- if (!funcName) {
408
- let nameNode = node.childForFieldName?.('name');
409
- if (!nameNode) {
410
- for (let i = 0; i < node.childCount; i++) {
411
- const c = node.child(i);
412
- if (c?.type === 'identifier' ||
413
- c?.type === 'property_identifier' ||
414
- c?.type === 'simple_identifier') {
415
- nameNode = c;
416
- break;
417
- }
418
- }
419
- }
420
- funcName = nameNode?.text;
421
- }
422
- }
423
- else if (node.type === 'impl_item') {
424
- let funcItem = null;
425
- for (let i = 0; i < node.childCount; i++) {
426
- const c = node.child(i);
427
- if (c?.type === 'function_item') {
428
- funcItem = c;
429
- break;
430
- }
431
- }
432
- if (funcItem) {
433
- let nameNode = funcItem.childForFieldName?.('name');
434
- if (!nameNode) {
435
- for (let i = 0; i < funcItem.childCount; i++) {
436
- const c = funcItem.child(i);
437
- if (c?.type === 'identifier') {
438
- nameNode = c;
439
- break;
440
- }
441
- }
442
- }
443
- funcName = nameNode?.text;
444
- label = 'Method';
445
- }
446
- }
447
- else if (node.type === 'method_definition') {
448
- let nameNode = node.childForFieldName?.('name');
449
- if (!nameNode) {
450
- for (let i = 0; i < node.childCount; i++) {
451
- const c = node.child(i);
452
- if (c?.type === 'property_identifier') {
453
- nameNode = c;
454
- break;
455
- }
456
- }
457
- }
458
- funcName = nameNode?.text;
459
- label = 'Method';
460
- }
461
- else if (node.type === 'method_declaration' || node.type === 'constructor_declaration') {
462
- let nameNode = node.childForFieldName?.('name');
463
- if (!nameNode) {
464
- for (let i = 0; i < node.childCount; i++) {
465
- const c = node.child(i);
466
- if (c?.type === 'identifier') {
467
- nameNode = c;
468
- break;
469
- }
470
- }
471
- }
472
- funcName = nameNode?.text;
473
- label = 'Method';
474
- }
475
- else if (node.type === 'arrow_function' || node.type === 'function_expression') {
476
- const parent = node.parent;
477
- if (parent?.type === 'variable_declarator') {
478
- let nameNode = parent.childForFieldName?.('name');
479
- if (!nameNode) {
480
- for (let i = 0; i < parent.childCount; i++) {
481
- const c = parent.child(i);
482
- if (c?.type === 'identifier') {
483
- nameNode = c;
484
- break;
485
- }
486
- }
487
- }
488
- funcName = nameNode?.text;
489
- }
345
+ /** Generic name extraction from a function-like AST node.
346
+ * Tries `node.childForFieldName('name')?.text`, then scans children for
347
+ * `identifier` / `property_identifier` / `simple_identifier`. */
348
+ export const genericFuncName = (node) => {
349
+ const nameField = node.childForFieldName?.('name');
350
+ if (nameField)
351
+ return nameField.text;
352
+ for (let i = 0; i < node.childCount; i++) {
353
+ const c = node.child(i);
354
+ if (c?.type === 'identifier' ||
355
+ c?.type === 'property_identifier' ||
356
+ c?.type === 'simple_identifier')
357
+ return c.text;
490
358
  }
491
- else if (node.type === 'method' || node.type === 'singleton_method') {
492
- let nameNode = node.childForFieldName?.('name');
493
- if (!nameNode) {
494
- for (let i = 0; i < node.childCount; i++) {
495
- const c = node.child(i);
496
- if (c?.type === 'identifier') {
497
- nameNode = c;
498
- break;
499
- }
500
- }
501
- }
502
- funcName = nameNode?.text;
503
- label = 'Method';
504
- }
505
- else if (node.type === 'function_signature') {
506
- // Dart: top-level function signatures
507
- let nameNode = node.childForFieldName?.('name');
508
- if (!nameNode) {
509
- for (let i = 0; i < node.childCount; i++) {
510
- const c = node.child(i);
511
- if (c?.type === 'identifier') {
512
- nameNode = c;
513
- break;
514
- }
515
- }
516
- }
517
- funcName = nameNode?.text ?? null;
518
- }
519
- else if (node.type === 'method_signature') {
520
- // Dart: method_signature wraps function_signature
521
- let funcSig = null;
522
- for (let i = 0; i < node.childCount; i++) {
523
- const c = node.child(i);
524
- if (c?.type === 'function_signature') {
525
- funcSig = c;
526
- break;
527
- }
528
- }
529
- if (funcSig) {
530
- let nameNode = funcSig.childForFieldName?.('name');
531
- if (!nameNode) {
532
- for (let i = 0; i < funcSig.childCount; i++) {
533
- const c = funcSig.child(i);
534
- if (c?.type === 'identifier') {
535
- nameNode = c;
536
- break;
537
- }
538
- }
539
- }
540
- funcName = nameNode?.text ?? null;
541
- }
542
- label = 'Method';
543
- }
544
- return { funcName, label };
359
+ return null;
545
360
  };
546
- /** Argument list node types shared between extractMethodSignature and countCallArguments. */
361
+ /** AST node types that represent a method definition (for `inferFunctionLabel`). */
362
+ export const METHOD_LABEL_NODE_TYPES = new Set([
363
+ 'method_definition',
364
+ 'method_declaration',
365
+ 'method',
366
+ 'singleton_method',
367
+ ]);
368
+ /** AST node types that represent a constructor definition (for `inferFunctionLabel`). */
369
+ export const CONSTRUCTOR_LABEL_NODE_TYPES = new Set([
370
+ 'constructor_declaration',
371
+ 'compact_constructor_declaration',
372
+ ]);
373
+ /** Infer node label from AST node type for function-like nodes without a provider hook. */
374
+ export const inferFunctionLabel = (nodeType) => METHOD_LABEL_NODE_TYPES.has(nodeType)
375
+ ? 'Method'
376
+ : CONSTRUCTOR_LABEL_NODE_TYPES.has(nodeType)
377
+ ? 'Constructor'
378
+ : 'Function';
379
+ /** Argument list node types shared between countCallArguments and call-resolution helpers. */
547
380
  export const CALL_ARGUMENT_LIST_TYPES = new Set(['arguments', 'argument_list', 'value_arguments']);
548
- /**
549
- * Extract parameter count and return type text from an AST method/function node.
550
- * Works across languages by looking for common AST patterns.
551
- */
552
- export const extractMethodSignature = (node) => {
553
- let parameterCount = 0;
554
- let requiredCount = 0;
555
- let returnType;
556
- let isVariadic = false;
557
- const paramTypes = [];
558
- if (!node)
559
- return {
560
- parameterCount,
561
- requiredParameterCount: undefined,
562
- parameterTypes: undefined,
563
- returnType,
564
- };
565
- const paramListTypes = new Set([
566
- 'formal_parameters',
567
- 'parameters',
568
- 'parameter_list',
569
- 'function_parameters',
570
- 'method_parameters',
571
- 'function_value_parameters',
572
- 'formal_parameter_list', // Dart
573
- ]);
574
- // Node types that indicate variadic/rest parameters
575
- const VARIADIC_PARAM_TYPES = new Set([
576
- 'variadic_parameter_declaration', // Go: ...string
577
- 'variadic_parameter', // Rust: extern "C" fn(...)
578
- 'spread_parameter', // Java: Object... args
579
- 'list_splat_pattern', // Python: *args
580
- 'dictionary_splat_pattern', // Python: **kwargs
581
- ]);
582
- /** AST node types that represent parameters with default values. */
583
- const OPTIONAL_PARAM_TYPES = new Set([
584
- 'optional_parameter', // TypeScript, Ruby: (x?: number), (x: number = 5), def f(x = 5)
585
- 'default_parameter', // Python: def f(x=5)
586
- 'typed_default_parameter', // Python: def f(x: int = 5)
587
- 'optional_parameter_declaration', // C++: void f(int x = 5)
588
- ]);
589
- /** Check if a parameter node has a default value (handles Kotlin, C#, Swift, PHP
590
- * where defaults are expressed as child nodes rather than distinct node types). */
591
- const hasDefaultValue = (paramNode) => {
592
- if (OPTIONAL_PARAM_TYPES.has(paramNode.type))
593
- return true;
594
- // C#, Swift, PHP: check for '=' token or equals_value_clause child
595
- for (let i = 0; i < paramNode.childCount; i++) {
596
- const c = paramNode.child(i);
597
- if (!c)
598
- continue;
599
- if (c.type === '=' || c.type === 'equals_value_clause')
600
- return true;
601
- }
602
- // Kotlin: default values are siblings of the parameter node, not children.
603
- // The AST is: parameter, =, <literal> — all at function_value_parameters level.
604
- // Check if the immediately following sibling is '=' (default value separator).
605
- const sib = paramNode.nextSibling;
606
- if (sib && sib.type === '=')
607
- return true;
608
- return false;
609
- };
610
- const findParameterList = (current) => {
611
- for (const child of current.children) {
612
- if (paramListTypes.has(child.type))
613
- return child;
614
- }
615
- for (const child of current.children) {
616
- const nested = findParameterList(child);
617
- if (nested)
618
- return nested;
619
- }
620
- return null;
621
- };
622
- const parameterList = paramListTypes.has(node.type)
623
- ? node // node itself IS the parameter list (e.g. C# primary constructors)
624
- : (node.childForFieldName?.('parameters') ?? findParameterList(node));
625
- if (parameterList && paramListTypes.has(parameterList.type)) {
626
- for (const param of parameterList.namedChildren) {
627
- if (param.type === 'comment')
628
- continue;
629
- if (param.text === 'self' ||
630
- param.text === '&self' ||
631
- param.text === '&mut self' ||
632
- param.type === 'self_parameter') {
633
- continue;
634
- }
635
- // Kotlin: default values are siblings of the parameter node inside
636
- // function_value_parameters, so they appear as named children (e.g.
637
- // string_literal, integer_literal, boolean_literal, call_expression).
638
- // Skip any named child that isn't a parameter-like or modifier node.
639
- if (param.type.endsWith('_literal') ||
640
- param.type === 'call_expression' ||
641
- param.type === 'navigation_expression' ||
642
- param.type === 'prefix_expression' ||
643
- param.type === 'parenthesized_expression') {
644
- continue;
645
- }
646
- // Check for variadic parameter types
647
- if (VARIADIC_PARAM_TYPES.has(param.type)) {
648
- isVariadic = true;
649
- continue;
650
- }
651
- // TypeScript/JavaScript: rest parameter — required_parameter containing rest_pattern
652
- if (param.type === 'required_parameter' || param.type === 'optional_parameter') {
653
- for (const child of param.children) {
654
- if (child.type === 'rest_pattern') {
655
- isVariadic = true;
656
- break;
657
- }
658
- }
659
- if (isVariadic)
660
- continue;
661
- }
662
- // Kotlin: vararg modifier on a regular parameter
663
- if (param.type === 'parameter' || param.type === 'formal_parameter') {
664
- const prev = param.previousSibling;
665
- if (prev?.type === 'parameter_modifiers' && prev.text.includes('vararg')) {
666
- isVariadic = true;
667
- }
668
- }
669
- // Extract parameter type name for overload disambiguation.
670
- // Works for Java (formal_parameter), Kotlin (parameter), C# (parameter),
671
- // C++ (parameter_declaration). Uses childForFieldName('type') which is the
672
- // standard tree-sitter field for typed parameters across these languages.
673
- // Kotlin uses positional children instead of 'type' field — fall back to
674
- // searching for user_type/nullable_type/predefined_type children.
675
- const paramTypeNode = param.childForFieldName('type');
676
- if (paramTypeNode) {
677
- const typeName = extractSimpleTypeName(paramTypeNode);
678
- paramTypes.push(typeName ?? 'unknown');
679
- }
680
- else {
681
- // Kotlin: parameter → [simple_identifier, user_type|nullable_type]
682
- let found = false;
683
- for (const child of param.namedChildren) {
684
- if (child.type === 'user_type' ||
685
- child.type === 'nullable_type' ||
686
- child.type === 'type_identifier' ||
687
- child.type === 'predefined_type') {
688
- const typeName = extractSimpleTypeName(child);
689
- paramTypes.push(typeName ?? 'unknown');
690
- found = true;
691
- break;
692
- }
693
- }
694
- if (!found)
695
- paramTypes.push('unknown');
696
- }
697
- if (!hasDefaultValue(param))
698
- requiredCount++;
699
- parameterCount++;
700
- }
701
- // C/C++: bare `...` token in parameter list (not a named child — check all children)
702
- if (!isVariadic) {
703
- for (const child of parameterList.children) {
704
- if (!child.isNamed && child.text === '...') {
705
- isVariadic = true;
706
- break;
707
- }
708
- }
709
- }
710
- }
711
- // Swift fallback: tree-sitter-swift places `parameter` nodes as direct children of
712
- // function_declaration without a wrapping parameters/function_parameters list node.
713
- // When no parameter list was found, count direct `parameter` children on the node.
714
- if (!parameterList && parameterCount === 0) {
715
- for (const child of node.namedChildren) {
716
- if (child.type === 'parameter') {
717
- if (!hasDefaultValue(child))
718
- requiredCount++;
719
- parameterCount++;
720
- }
721
- }
722
- }
723
- // Return type extraction — language-specific field names
724
- // Go: 'result' field is either a type_identifier or parameter_list (multi-return)
725
- const goResult = node.childForFieldName?.('result');
726
- if (goResult) {
727
- if (goResult.type === 'parameter_list') {
728
- // Multi-return: extract first parameter's type only (e.g. (*User, error) → *User)
729
- const firstParam = goResult.firstNamedChild;
730
- if (firstParam?.type === 'parameter_declaration') {
731
- const typeNode = firstParam.childForFieldName('type');
732
- if (typeNode)
733
- returnType = typeNode.text;
734
- }
735
- else if (firstParam) {
736
- // Unnamed return types: (string, error) — first child is a bare type node
737
- returnType = firstParam.text;
738
- }
739
- }
740
- else {
741
- returnType = goResult.text;
742
- }
743
- }
744
- // Rust: 'return_type' field — the value IS the type node (e.g. primitive_type, type_identifier).
745
- // Skip if the node is a type_annotation (TS/Python), which is handled by the generic loop below.
746
- if (!returnType) {
747
- const rustReturn = node.childForFieldName?.('return_type');
748
- if (rustReturn && rustReturn.type !== 'type_annotation') {
749
- returnType = rustReturn.text;
750
- }
751
- }
752
- // C/C++: 'type' field on function_definition
753
- if (!returnType) {
754
- const cppType = node.childForFieldName?.('type');
755
- if (cppType && cppType.text !== 'void') {
756
- returnType = cppType.text;
757
- }
758
- }
759
- // C#: 'returns' field on method_declaration
760
- if (!returnType) {
761
- const csReturn = node.childForFieldName?.('returns');
762
- if (csReturn && csReturn.text !== 'void') {
763
- returnType = csReturn.text;
764
- }
765
- }
766
- // TS/Rust/Python/C#/Kotlin: type_annotation or return_type child
767
- if (!returnType) {
768
- for (const child of node.children) {
769
- if (child.type === 'type_annotation' || child.type === 'return_type') {
770
- const typeNode = child.children.find((c) => c.isNamed);
771
- if (typeNode)
772
- returnType = typeNode.text;
773
- }
774
- }
775
- }
776
- // Kotlin: fun getUser(): User — return type is a bare user_type child of
777
- // function_declaration. The Kotlin grammar does NOT wrap it in type_annotation
778
- // or return_type; it appears as a direct child after function_value_parameters.
779
- // Note: Kotlin uses function_value_parameters (not a field), so we find it by type.
780
- if (!returnType) {
781
- let paramsEnd = -1;
782
- for (let i = 0; i < node.childCount; i++) {
783
- const child = node.child(i);
784
- if (!child)
785
- continue;
786
- if (child.type === 'function_value_parameters' || child.type === 'value_parameters') {
787
- paramsEnd = child.endIndex;
788
- }
789
- if (paramsEnd >= 0 && child.type === 'user_type' && child.startIndex > paramsEnd) {
790
- returnType = child.text;
791
- break;
792
- }
793
- }
794
- }
795
- if (isVariadic)
796
- parameterCount = undefined;
797
- // Only include parameterTypes when at least one type was successfully extracted.
798
- // Use undefined (not []) to avoid empty array allocations for untyped parameters.
799
- const hasTypes = paramTypes.length > 0 && paramTypes.some((t) => t !== 'unknown');
800
- // Only set requiredParameterCount when it differs from total — saves memory on the common case.
801
- const requiredParameterCount = !isVariadic && requiredCount < (parameterCount ?? 0) ? requiredCount : undefined;
802
- return {
803
- parameterCount,
804
- requiredParameterCount,
805
- parameterTypes: hasTypes ? paramTypes : undefined,
806
- returnType,
807
- };
808
- };
809
381
  // ============================================================================
810
382
  // Generic AST traversal helpers (shared by parse-worker + php-helpers)
811
383
  // ============================================================================
812
384
  /** Walk an AST node depth-first, returning the first descendant with the given type. */
813
- export function findDescendant(node, type) {
814
- if (node.type === type)
815
- return node;
816
- for (const child of node.children ?? []) {
817
- const found = findDescendant(child, type);
818
- if (found)
819
- return found;
385
+ export function findDescendant(root, type) {
386
+ const stack = [root];
387
+ while (stack.length > 0) {
388
+ const node = stack.pop();
389
+ if (node.type === type)
390
+ return node;
391
+ // Push in reverse order so left children are visited first (depth-first)
392
+ const children = node.children ?? [];
393
+ for (let i = children.length - 1; i >= 0; i--) {
394
+ stack.push(children[i]);
395
+ }
820
396
  }
821
397
  return null;
822
398
  }
@@ -831,18 +407,6 @@ export function extractStringContent(node) {
831
407
  return node.text;
832
408
  return null;
833
409
  }
834
- /** Check if a C/C++ function_definition is inside a class or struct body.
835
- * Used by the C/C++ labelOverride to skip duplicate function captures
836
- * that are already covered by definition.method queries. */
837
- export function isCppInsideClassOrStruct(functionNode) {
838
- let ancestor = functionNode?.parent ?? null;
839
- while (ancestor) {
840
- if (ancestor.type === 'class_specifier' || ancestor.type === 'struct_specifier')
841
- return true;
842
- ancestor = ancestor.parent;
843
- }
844
- return false;
845
- }
846
410
  /** Find the first direct named child of a tree-sitter node matching the given type. */
847
411
  export function findChild(node, type) {
848
412
  for (let i = 0; i < node.namedChildCount; i++) {