gitnexus 1.4.9 → 1.5.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 (186) hide show
  1. package/README.md +6 -5
  2. package/dist/cli/ai-context.d.ts +4 -1
  3. package/dist/cli/ai-context.js +19 -11
  4. package/dist/cli/analyze.d.ts +6 -0
  5. package/dist/cli/analyze.js +105 -251
  6. package/dist/cli/eval-server.js +20 -11
  7. package/dist/cli/index-repo.js +20 -22
  8. package/dist/cli/index.js +8 -7
  9. package/dist/cli/mcp.js +1 -1
  10. package/dist/cli/serve.js +29 -1
  11. package/dist/cli/setup.js +9 -9
  12. package/dist/cli/skill-gen.js +15 -9
  13. package/dist/cli/wiki.d.ts +2 -0
  14. package/dist/cli/wiki.js +141 -26
  15. package/dist/config/ignore-service.js +102 -22
  16. package/dist/config/supported-languages.d.ts +8 -42
  17. package/dist/config/supported-languages.js +8 -43
  18. package/dist/core/augmentation/engine.js +19 -7
  19. package/dist/core/embeddings/embedder.js +19 -15
  20. package/dist/core/embeddings/embedding-pipeline.js +6 -6
  21. package/dist/core/embeddings/http-client.js +3 -3
  22. package/dist/core/embeddings/text-generator.js +9 -24
  23. package/dist/core/embeddings/types.d.ts +1 -1
  24. package/dist/core/embeddings/types.js +1 -7
  25. package/dist/core/graph/graph.js +6 -2
  26. package/dist/core/graph/types.d.ts +9 -59
  27. package/dist/core/ingestion/ast-cache.js +3 -3
  28. package/dist/core/ingestion/call-processor.d.ts +20 -2
  29. package/dist/core/ingestion/call-processor.js +347 -144
  30. package/dist/core/ingestion/call-routing.js +10 -4
  31. package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +10 -0
  32. package/dist/core/ingestion/call-sites/extract-language-call-site.js +22 -0
  33. package/dist/core/ingestion/call-sites/java.d.ts +9 -0
  34. package/dist/core/ingestion/call-sites/java.js +30 -0
  35. package/dist/core/ingestion/cluster-enricher.js +6 -8
  36. package/dist/core/ingestion/cobol/cobol-copy-expander.js +10 -3
  37. package/dist/core/ingestion/cobol/cobol-preprocessor.js +287 -81
  38. package/dist/core/ingestion/cobol/jcl-parser.js +1 -1
  39. package/dist/core/ingestion/cobol/jcl-processor.js +1 -1
  40. package/dist/core/ingestion/cobol-processor.js +102 -56
  41. package/dist/core/ingestion/community-processor.js +21 -15
  42. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -1
  43. package/dist/core/ingestion/entry-point-scoring.js +5 -6
  44. package/dist/core/ingestion/export-detection.js +32 -9
  45. package/dist/core/ingestion/field-extractor.d.ts +1 -1
  46. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +8 -12
  47. package/dist/core/ingestion/field-extractors/configs/csharp.js +45 -2
  48. package/dist/core/ingestion/field-extractors/configs/dart.js +5 -3
  49. package/dist/core/ingestion/field-extractors/configs/go.js +3 -7
  50. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -0
  51. package/dist/core/ingestion/field-extractors/configs/helpers.js +14 -0
  52. package/dist/core/ingestion/field-extractors/configs/jvm.js +7 -7
  53. package/dist/core/ingestion/field-extractors/configs/php.js +9 -11
  54. package/dist/core/ingestion/field-extractors/configs/python.js +1 -1
  55. package/dist/core/ingestion/field-extractors/configs/ruby.js +4 -3
  56. package/dist/core/ingestion/field-extractors/configs/rust.js +2 -5
  57. package/dist/core/ingestion/field-extractors/configs/swift.js +9 -7
  58. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +2 -6
  59. package/dist/core/ingestion/field-extractors/generic.d.ts +5 -2
  60. package/dist/core/ingestion/field-extractors/generic.js +6 -0
  61. package/dist/core/ingestion/field-extractors/typescript.d.ts +1 -1
  62. package/dist/core/ingestion/field-extractors/typescript.js +1 -1
  63. package/dist/core/ingestion/field-types.d.ts +4 -2
  64. package/dist/core/ingestion/filesystem-walker.js +3 -3
  65. package/dist/core/ingestion/framework-detection.d.ts +1 -1
  66. package/dist/core/ingestion/framework-detection.js +355 -85
  67. package/dist/core/ingestion/heritage-processor.d.ts +24 -0
  68. package/dist/core/ingestion/heritage-processor.js +99 -8
  69. package/dist/core/ingestion/import-processor.js +44 -15
  70. package/dist/core/ingestion/import-resolvers/csharp.js +7 -3
  71. package/dist/core/ingestion/import-resolvers/dart.js +1 -1
  72. package/dist/core/ingestion/import-resolvers/go.js +4 -2
  73. package/dist/core/ingestion/import-resolvers/jvm.js +4 -4
  74. package/dist/core/ingestion/import-resolvers/php.js +4 -4
  75. package/dist/core/ingestion/import-resolvers/python.js +1 -1
  76. package/dist/core/ingestion/import-resolvers/rust.js +9 -3
  77. package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
  78. package/dist/core/ingestion/import-resolvers/standard.js +6 -5
  79. package/dist/core/ingestion/import-resolvers/swift.js +2 -1
  80. package/dist/core/ingestion/import-resolvers/utils.js +26 -7
  81. package/dist/core/ingestion/language-config.js +5 -4
  82. package/dist/core/ingestion/language-provider.d.ts +7 -2
  83. package/dist/core/ingestion/languages/c-cpp.js +106 -21
  84. package/dist/core/ingestion/languages/cobol.js +1 -1
  85. package/dist/core/ingestion/languages/csharp.js +96 -19
  86. package/dist/core/ingestion/languages/dart.js +23 -7
  87. package/dist/core/ingestion/languages/go.js +1 -1
  88. package/dist/core/ingestion/languages/index.d.ts +1 -1
  89. package/dist/core/ingestion/languages/index.js +2 -3
  90. package/dist/core/ingestion/languages/java.js +4 -1
  91. package/dist/core/ingestion/languages/kotlin.js +60 -13
  92. package/dist/core/ingestion/languages/php.js +102 -25
  93. package/dist/core/ingestion/languages/python.js +28 -5
  94. package/dist/core/ingestion/languages/ruby.js +56 -14
  95. package/dist/core/ingestion/languages/rust.js +55 -11
  96. package/dist/core/ingestion/languages/swift.js +112 -27
  97. package/dist/core/ingestion/languages/typescript.js +95 -19
  98. package/dist/core/ingestion/markdown-processor.js +5 -5
  99. package/dist/core/ingestion/method-extractors/configs/csharp.d.ts +2 -0
  100. package/dist/core/ingestion/method-extractors/configs/csharp.js +283 -0
  101. package/dist/core/ingestion/method-extractors/configs/jvm.d.ts +3 -0
  102. package/dist/core/ingestion/method-extractors/configs/jvm.js +326 -0
  103. package/dist/core/ingestion/method-extractors/generic.d.ts +5 -0
  104. package/dist/core/ingestion/method-extractors/generic.js +137 -0
  105. package/dist/core/ingestion/method-types.d.ts +61 -0
  106. package/dist/core/ingestion/method-types.js +2 -0
  107. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  108. package/dist/core/ingestion/mro-processor.js +12 -8
  109. package/dist/core/ingestion/named-binding-processor.js +2 -2
  110. package/dist/core/ingestion/named-bindings/rust.js +3 -1
  111. package/dist/core/ingestion/parsing-processor.js +74 -24
  112. package/dist/core/ingestion/pipeline.d.ts +2 -1
  113. package/dist/core/ingestion/pipeline.js +208 -102
  114. package/dist/core/ingestion/process-processor.js +12 -10
  115. package/dist/core/ingestion/resolution-context.js +3 -3
  116. package/dist/core/ingestion/route-extractors/middleware.js +31 -7
  117. package/dist/core/ingestion/route-extractors/php.js +2 -1
  118. package/dist/core/ingestion/route-extractors/response-shapes.js +8 -4
  119. package/dist/core/ingestion/structure-processor.d.ts +1 -1
  120. package/dist/core/ingestion/structure-processor.js +4 -4
  121. package/dist/core/ingestion/symbol-table.d.ts +1 -1
  122. package/dist/core/ingestion/symbol-table.js +22 -6
  123. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  124. package/dist/core/ingestion/tree-sitter-queries.js +1 -1
  125. package/dist/core/ingestion/type-env.d.ts +2 -2
  126. package/dist/core/ingestion/type-env.js +75 -50
  127. package/dist/core/ingestion/type-extractors/c-cpp.js +33 -30
  128. package/dist/core/ingestion/type-extractors/csharp.js +24 -14
  129. package/dist/core/ingestion/type-extractors/dart.js +6 -8
  130. package/dist/core/ingestion/type-extractors/go.js +7 -6
  131. package/dist/core/ingestion/type-extractors/jvm.js +10 -21
  132. package/dist/core/ingestion/type-extractors/php.js +26 -13
  133. package/dist/core/ingestion/type-extractors/python.js +11 -15
  134. package/dist/core/ingestion/type-extractors/ruby.js +8 -3
  135. package/dist/core/ingestion/type-extractors/rust.js +6 -8
  136. package/dist/core/ingestion/type-extractors/shared.js +134 -50
  137. package/dist/core/ingestion/type-extractors/swift.js +16 -13
  138. package/dist/core/ingestion/type-extractors/typescript.js +23 -15
  139. package/dist/core/ingestion/utils/ast-helpers.d.ts +8 -8
  140. package/dist/core/ingestion/utils/ast-helpers.js +72 -35
  141. package/dist/core/ingestion/utils/call-analysis.d.ts +2 -0
  142. package/dist/core/ingestion/utils/call-analysis.js +96 -49
  143. package/dist/core/ingestion/utils/event-loop.js +1 -1
  144. package/dist/core/ingestion/workers/parse-worker.d.ts +7 -2
  145. package/dist/core/ingestion/workers/parse-worker.js +364 -84
  146. package/dist/core/ingestion/workers/worker-pool.js +5 -10
  147. package/dist/core/lbug/csv-generator.js +54 -15
  148. package/dist/core/lbug/lbug-adapter.d.ts +5 -0
  149. package/dist/core/lbug/lbug-adapter.js +86 -23
  150. package/dist/core/lbug/schema.d.ts +3 -6
  151. package/dist/core/lbug/schema.js +6 -30
  152. package/dist/core/run-analyze.d.ts +49 -0
  153. package/dist/core/run-analyze.js +257 -0
  154. package/dist/core/tree-sitter/parser-loader.d.ts +1 -1
  155. package/dist/core/tree-sitter/parser-loader.js +1 -1
  156. package/dist/core/wiki/cursor-client.js +2 -7
  157. package/dist/core/wiki/generator.js +38 -23
  158. package/dist/core/wiki/graph-queries.js +10 -10
  159. package/dist/core/wiki/html-viewer.js +7 -3
  160. package/dist/core/wiki/llm-client.d.ts +23 -2
  161. package/dist/core/wiki/llm-client.js +96 -26
  162. package/dist/core/wiki/prompts.js +7 -6
  163. package/dist/mcp/core/embedder.js +1 -1
  164. package/dist/mcp/core/lbug-adapter.d.ts +4 -1
  165. package/dist/mcp/core/lbug-adapter.js +17 -7
  166. package/dist/mcp/local/local-backend.js +247 -95
  167. package/dist/mcp/resources.js +14 -6
  168. package/dist/mcp/server.js +13 -5
  169. package/dist/mcp/staleness.js +5 -1
  170. package/dist/mcp/tools.js +100 -23
  171. package/dist/server/analyze-job.d.ts +53 -0
  172. package/dist/server/analyze-job.js +146 -0
  173. package/dist/server/analyze-worker.d.ts +13 -0
  174. package/dist/server/analyze-worker.js +59 -0
  175. package/dist/server/api.js +795 -44
  176. package/dist/server/git-clone.d.ts +25 -0
  177. package/dist/server/git-clone.js +91 -0
  178. package/dist/storage/git.js +1 -3
  179. package/dist/storage/repo-manager.d.ts +5 -2
  180. package/dist/storage/repo-manager.js +4 -4
  181. package/dist/types/pipeline.d.ts +1 -21
  182. package/dist/types/pipeline.js +1 -18
  183. package/hooks/claude/gitnexus-hook.cjs +52 -22
  184. package/package.json +13 -13
  185. package/dist/core/ingestion/utils/language-detection.d.ts +0 -9
  186. package/dist/core/ingestion/utils/language-detection.js +0 -70
@@ -1,5 +1,5 @@
1
1
  import { findChild } from '../utils/ast-helpers.js';
2
- import { extractSimpleTypeName, extractVarName, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
2
+ import { extractSimpleTypeName, extractVarName, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString, } from './shared.js';
3
3
  // ── Java ──────────────────────────────────────────────────────────────────
4
4
  const JAVA_DECLARATION_NODE_TYPES = new Set([
5
5
  'local_variable_declaration',
@@ -95,9 +95,7 @@ const scanJavaConstructorBinding = (node) => {
95
95
  return undefined;
96
96
  return { varName: nameNode.text, calleeName: methodName.text };
97
97
  };
98
- const JAVA_FOR_LOOP_NODE_TYPES = new Set([
99
- 'enhanced_for_statement',
100
- ]);
98
+ const JAVA_FOR_LOOP_NODE_TYPES = new Set(['enhanced_for_statement']);
101
99
  /** Extract element type from a Java type annotation AST node.
102
100
  * Handles generic_type (List<User>), array_type (User[]). */
103
101
  const extractJavaElementTypeFromTypeNode = (typeNode, pos = 'last') => {
@@ -352,8 +350,7 @@ const extractKotlinDeclaration = (node, env) => {
352
350
  const varDecl = findChild(node, 'variable_declaration');
353
351
  if (varDecl) {
354
352
  const nameNode = findChild(varDecl, 'simple_identifier');
355
- const typeNode = findChild(varDecl, 'user_type')
356
- ?? findChild(varDecl, 'nullable_type');
353
+ const typeNode = findChild(varDecl, 'user_type') ?? findChild(varDecl, 'nullable_type');
357
354
  if (!nameNode || !typeNode)
358
355
  return;
359
356
  const varName = extractVarName(nameNode);
@@ -363,10 +360,8 @@ const extractKotlinDeclaration = (node, env) => {
363
360
  return;
364
361
  }
365
362
  // Fallback: try direct fields
366
- const nameNode = node.childForFieldName('name')
367
- ?? findChild(node, 'simple_identifier');
368
- const typeNode = node.childForFieldName('type')
369
- ?? findChild(node, 'user_type');
363
+ const nameNode = node.childForFieldName('name') ?? findChild(node, 'simple_identifier');
364
+ const typeNode = node.childForFieldName('type') ?? findChild(node, 'user_type');
370
365
  if (!nameNode || !typeNode)
371
366
  return;
372
367
  const varName = extractVarName(nameNode);
@@ -405,8 +400,7 @@ const extractKotlinParameter = (node, env) => {
405
400
  if (!nameNode)
406
401
  nameNode = findChild(node, 'simple_identifier');
407
402
  if (!typeNode)
408
- typeNode = findChild(node, 'user_type')
409
- ?? findChild(node, 'nullable_type');
403
+ typeNode = findChild(node, 'user_type') ?? findChild(node, 'nullable_type');
410
404
  if (!nameNode || !typeNode)
411
405
  return;
412
406
  const varName = extractVarName(nameNode);
@@ -419,8 +413,7 @@ const extractKotlinParameter = (node, env) => {
419
413
  const findKotlinConstructorCallee = (node, classNames) => {
420
414
  if (node.type !== 'property_declaration')
421
415
  return undefined;
422
- const value = node.childForFieldName('value')
423
- ?? findChild(node, 'call_expression');
416
+ const value = node.childForFieldName('value') ?? findChild(node, 'call_expression');
424
417
  if (!value || value.type !== 'call_expression')
425
418
  return undefined;
426
419
  const callee = value.firstNamedChild;
@@ -495,9 +488,7 @@ const scanKotlinConstructorBinding = (node) => {
495
488
  return undefined;
496
489
  return { varName: nameNode.text, calleeName };
497
490
  };
498
- const KOTLIN_FOR_LOOP_NODE_TYPES = new Set([
499
- 'for_statement',
500
- ]);
491
+ const KOTLIN_FOR_LOOP_NODE_TYPES = new Set(['for_statement']);
501
492
  /** Extract element type from a Kotlin type annotation AST node (user_type wrapping generic).
502
493
  * Kotlin: user_type → [type_identifier, type_arguments → [type_projection → user_type]]
503
494
  * Handles the type_projection wrapper that Kotlin uses for generic type arguments. */
@@ -511,9 +502,7 @@ const extractKotlinElementTypeFromTypeNode = (typeNode, pos = 'last') => {
511
502
  if (!targetArg)
512
503
  return undefined;
513
504
  // Kotlin wraps type args in type_projection — unwrap to get the inner type
514
- const inner = targetArg.type === 'type_projection'
515
- ? targetArg.firstNamedChild
516
- : targetArg;
505
+ const inner = targetArg.type === 'type_projection' ? targetArg.firstNamedChild : targetArg;
517
506
  if (inner)
518
507
  return extractSimpleTypeName(inner);
519
508
  }
@@ -792,7 +781,7 @@ const extractKotlinPatternBinding = (node, scopeEnv, declarationTypeNodes, scope
792
781
  // Kotlin AST: equality_expression > simple_identifier, "!=" [anon], "null" [anon]
793
782
  // Note: `null` is an anonymous node in tree-sitter-kotlin, not `null_literal`.
794
783
  if (node.type === 'equality_expression') {
795
- const op = node.children.find(c => !c.isNamed && c.text === '!=');
784
+ const op = node.children.find((c) => !c.isNamed && c.text === '!=');
796
785
  if (!op)
797
786
  return undefined;
798
787
  // `null` is anonymous in Kotlin grammar — use positional child scan
@@ -1,4 +1,4 @@
1
- import { extractSimpleTypeName, extractVarName, extractCalleeName, resolveIterableElementType, extractElementTypeFromString } from './shared.js';
1
+ import { extractSimpleTypeName, extractVarName, extractCalleeName, resolveIterableElementType, extractElementTypeFromString, } from './shared.js';
2
2
  const DECLARATION_NODE_TYPES = new Set([
3
3
  'assignment_expression', // For constructor inference: $x = new User()
4
4
  'property_declaration', // PHP 7.4+ typed properties: private UserRepo $repo;
@@ -51,7 +51,9 @@ const normalizePhpType = (raw) => {
51
51
  // Strip array suffix: User[] → User
52
52
  type = type.replace(/\[\]$/, '');
53
53
  // Strip union with null/false/void: User|null → User
54
- const parts = type.split('|').filter(p => p !== 'null' && p !== 'false' && p !== 'void' && p !== 'mixed');
54
+ const parts = type
55
+ .split('|')
56
+ .filter((p) => p !== 'null' && p !== 'false' && p !== 'void' && p !== 'mixed');
55
57
  if (parts.length !== 1)
56
58
  return undefined;
57
59
  type = parts[0];
@@ -59,7 +61,11 @@ const normalizePhpType = (raw) => {
59
61
  const segments = type.split('\\');
60
62
  type = segments[segments.length - 1];
61
63
  // Skip uninformative types
62
- if (type === 'mixed' || type === 'void' || type === 'self' || type === 'static' || type === 'object')
64
+ if (type === 'mixed' ||
65
+ type === 'void' ||
66
+ type === 'self' ||
67
+ type === 'static' ||
68
+ type === 'object')
63
69
  return undefined;
64
70
  // Extract element type from generic: Collection<User> → User
65
71
  // PHPDoc generics encode the element type in angle brackets. Since PHP's Strategy B
@@ -124,8 +130,8 @@ const extractClassPropertyElementType = (propDecl) => {
124
130
  * both fail to find the type.
125
131
  */
126
132
  const findClassPropertyElementType = (propName, classNode) => {
127
- const declList = classNode.childForFieldName('body')
128
- ?? (classNode.namedChild(classNode.namedChildCount - 1)?.type === 'declaration_list'
133
+ const declList = classNode.childForFieldName('body') ??
134
+ (classNode.namedChild(classNode.namedChildCount - 1)?.type === 'declaration_list'
129
135
  ? classNode.namedChild(classNode.namedChildCount - 1)
130
136
  : null); // fallback: last named child, only if it's a declaration_list
131
137
  if (!declList)
@@ -245,7 +251,7 @@ const extractInitializer = (node, env, _classNames) => {
245
251
  if (!typeName)
246
252
  return;
247
253
  // Resolve PHP self/static/parent to actual class names
248
- const resolvedType = (typeName === 'self' || typeName === 'static' || typeName === 'parent')
254
+ const resolvedType = typeName === 'self' || typeName === 'static' || typeName === 'parent'
249
255
  ? resolvePhpKeyword(typeName, node)
250
256
  : typeName;
251
257
  if (!resolvedType)
@@ -331,7 +337,9 @@ const normalizePhpReturnType = (raw) => {
331
337
  // Strip nullable prefix: ?User[] → User[]
332
338
  let type = raw.startsWith('?') ? raw.slice(1) : raw;
333
339
  // Strip union with null/false/void: User[]|null → User[]
334
- const parts = type.split('|').filter(p => p !== 'null' && p !== 'false' && p !== 'void' && p !== 'mixed');
340
+ const parts = type
341
+ .split('|')
342
+ .filter((p) => p !== 'null' && p !== 'false' && p !== 'void' && p !== 'mixed');
335
343
  if (parts.length !== 1)
336
344
  return undefined;
337
345
  type = parts[0];
@@ -339,7 +347,12 @@ const normalizePhpReturnType = (raw) => {
339
347
  const segments = type.split('\\');
340
348
  type = segments[segments.length - 1];
341
349
  // Skip uninformative types
342
- if (type === 'mixed' || type === 'void' || type === 'self' || type === 'static' || type === 'object' || type === 'array')
350
+ if (type === 'mixed' ||
351
+ type === 'void' ||
352
+ type === 'self' ||
353
+ type === 'static' ||
354
+ type === 'object' ||
355
+ type === 'array')
343
356
  return undefined;
344
357
  if (/^\w+(\[\])?$/.test(type) || /^\w+\s*</.test(type))
345
358
  return type;
@@ -408,9 +421,7 @@ const extractPendingAssignment = (node, scopeEnv) => {
408
421
  }
409
422
  return undefined;
410
423
  };
411
- const FOR_LOOP_NODE_TYPES = new Set([
412
- 'foreach_statement',
413
- ]);
424
+ const FOR_LOOP_NODE_TYPES = new Set(['foreach_statement']);
414
425
  /** Extract element type from a PHP type annotation AST node.
415
426
  * PHP has limited AST-level container types — `array` is a primitive_type with no generic args.
416
427
  * Named types (e.g., `Collection`) are returned as-is (container descriptor lookup handles them). */
@@ -482,11 +493,13 @@ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, re
482
493
  if (!lastChild)
483
494
  return;
484
495
  // Handle by_ref: foreach ($arr as $k => &$v)
485
- loopVarNode = lastChild.type === 'by_ref' ? (lastChild.firstNamedChild ?? lastChild) : lastChild;
496
+ loopVarNode =
497
+ lastChild.type === 'by_ref' ? (lastChild.firstNamedChild ?? lastChild) : lastChild;
486
498
  }
487
499
  else {
488
500
  // Simple: foreach ($users as $user) or foreach ($users as &$user)
489
- loopVarNode = valueOrPair.type === 'by_ref' ? (valueOrPair.firstNamedChild ?? valueOrPair) : valueOrPair;
501
+ loopVarNode =
502
+ valueOrPair.type === 'by_ref' ? (valueOrPair.firstNamedChild ?? valueOrPair) : valueOrPair;
490
503
  }
491
504
  const varName = extractVarName(loopVarNode);
492
505
  if (!varName)
@@ -1,4 +1,4 @@
1
- import { extractSimpleTypeName, extractVarName, extractElementTypeFromString, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition } from './shared.js';
1
+ import { extractSimpleTypeName, extractVarName, extractElementTypeFromString, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, } from './shared.js';
2
2
  const DECLARATION_NODE_TYPES = new Set([
3
3
  'assignment',
4
4
  'named_expression',
@@ -147,13 +147,9 @@ const scanConstructorBinding = (node) => {
147
147
  return undefined;
148
148
  return { varName: left.text, calleeName };
149
149
  };
150
- const FOR_LOOP_NODE_TYPES = new Set([
151
- 'for_statement',
152
- ]);
150
+ const FOR_LOOP_NODE_TYPES = new Set(['for_statement']);
153
151
  /** Python function/method node types that carry a parameters list. */
154
- const PY_FUNCTION_NODE_TYPES = new Set([
155
- 'function_definition', 'decorated_definition',
156
- ]);
152
+ const PY_FUNCTION_NODE_TYPES = new Set(['function_definition', 'decorated_definition']);
157
153
  /**
158
154
  * Extract element type from a Python type annotation AST node.
159
155
  * Handles:
@@ -215,13 +211,13 @@ const findPyParamElementType = (iterableName, startNode, pos = 'last') => {
215
211
  continue;
216
212
  // Try named `name` field first (parameter node), then first identifier child
217
213
  // (typed_parameter node may store name as first positional child)
218
- const nameNode = param.childForFieldName('name')
219
- ?? (param.firstNamedChild?.type === 'identifier' ? param.firstNamedChild : null);
214
+ const nameNode = param.childForFieldName('name') ??
215
+ (param.firstNamedChild?.type === 'identifier' ? param.firstNamedChild : null);
220
216
  if (nameNode?.text !== iterableName)
221
217
  continue;
222
218
  // Try `type` field, then last named child (typed_parameter stores type last)
223
- const typeAnnotation = param.childForFieldName('type')
224
- ?? (param.namedChildCount >= 2 ? param.namedChild(param.namedChildCount - 1) : null);
219
+ const typeAnnotation = param.childForFieldName('type') ??
220
+ (param.namedChildCount >= 2 ? param.namedChild(param.namedChildCount - 1) : null);
225
221
  if (typeAnnotation && typeAnnotation !== nameNode) {
226
222
  return extractPyElementTypeFromAnnotation(typeAnnotation, pos);
227
223
  }
@@ -245,7 +241,7 @@ const extractMethodCall = (callNode) => {
245
241
  if (obj?.type !== 'identifier')
246
242
  return undefined;
247
243
  const method = fn.lastNamedChild;
248
- const methodName = (method?.type === 'identifier' && method !== obj) ? method.text : undefined;
244
+ const methodName = method?.type === 'identifier' && method !== obj ? method.text : undefined;
249
245
  return { iterableName: obj.text, methodName };
250
246
  };
251
247
  /**
@@ -428,8 +424,7 @@ const extractPatternBinding = (node, scopeEnv) => {
428
424
  if (node.namedChildCount < 2)
429
425
  return undefined;
430
426
  const patternChild = node.namedChild(0);
431
- const varNameNode = node.childForFieldName('alias')
432
- ?? node.namedChild(node.namedChildCount - 1);
427
+ const varNameNode = node.childForFieldName('alias') ?? node.namedChild(node.namedChildCount - 1);
433
428
  if (!patternChild || !varNameNode)
434
429
  return undefined;
435
430
  if (varNameNode.type !== 'identifier')
@@ -456,7 +451,8 @@ const extractPatternBinding = (node, scopeEnv) => {
456
451
  return undefined;
457
452
  // class_pattern children: dotted_name (the class name) + optional keyword_pattern args.
458
453
  const classNameNode = classPattern.firstNamedChild;
459
- if (!classNameNode || (classNameNode.type !== 'dotted_name' && classNameNode.type !== 'identifier'))
454
+ if (!classNameNode ||
455
+ (classNameNode.type !== 'dotted_name' && classNameNode.type !== 'identifier'))
460
456
  return undefined;
461
457
  const typeName = classNameNode.text;
462
458
  if (!typeName)
@@ -1,4 +1,4 @@
1
- import { extractRubyConstructorAssignment, extractSimpleTypeName, extractElementTypeFromString, extractVarName, resolveIterableElementType } from './shared.js';
1
+ import { extractRubyConstructorAssignment, extractSimpleTypeName, extractElementTypeFromString, extractVarName, resolveIterableElementType, } from './shared.js';
2
2
  /**
3
3
  * Ruby type extractor — YARD annotation parsing.
4
4
  *
@@ -51,7 +51,7 @@ const extractYardTypeName = (yardType) => {
51
51
  }
52
52
  }
53
53
  parts.push(trimmed.slice(start).trim());
54
- const filtered = parts.filter(p => p !== '' && p !== 'nil');
54
+ const filtered = parts.filter((p) => p !== '' && p !== 'nil');
55
55
  if (filtered.length !== 1)
56
56
  return undefined; // ambiguous union
57
57
  const typePart = filtered[0];
@@ -386,7 +386,12 @@ const extractPendingAssignment = (node, scopeEnv) => {
386
386
  }
387
387
  if (receiverNode?.type === 'identifier' && methodNode?.type === 'identifier') {
388
388
  // With receiver → methodCallResult (a.method)
389
- return { kind: 'methodCallResult', lhs: varName, receiver: receiverNode.text, method: methodNode.text };
389
+ return {
390
+ kind: 'methodCallResult',
391
+ lhs: varName,
392
+ receiver: receiverNode.text,
393
+ method: methodNode.text,
394
+ };
390
395
  }
391
396
  }
392
397
  return undefined;
@@ -1,8 +1,5 @@
1
- import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
2
- const DECLARATION_NODE_TYPES = new Set([
3
- 'let_declaration',
4
- 'let_condition',
5
- ]);
1
+ import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString, } from './shared.js';
2
+ const DECLARATION_NODE_TYPES = new Set(['let_declaration', 'let_condition']);
6
3
  /** Walk up the AST to find the enclosing impl block and extract the implementing type name. */
7
4
  const findEnclosingImplType = (node) => {
8
5
  let current = node.parent;
@@ -308,9 +305,10 @@ const extractPatternBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
308
305
  // match_arm → pattern field is match_pattern wrapping the actual pattern
309
306
  const matchPatternNode = node.childForFieldName('pattern');
310
307
  // Unwrap match_pattern to get the tuple_struct_pattern inside
311
- patternNode = matchPatternNode?.type === 'match_pattern'
312
- ? matchPatternNode.firstNamedChild
313
- : matchPatternNode;
308
+ patternNode =
309
+ matchPatternNode?.type === 'match_pattern'
310
+ ? matchPatternNode.firstNamedChild
311
+ : matchPatternNode;
314
312
  // source variable is in the parent match_expression's 'value' field
315
313
  const matchExpr = node.parent?.parent; // match_arm → match_block → match_expression
316
314
  if (matchExpr?.type === 'match_expression') {
@@ -8,8 +8,18 @@ const CSHARP_KEY_METHODS = new Set(['Keys']);
8
8
  const STD_VALUE_METHODS = new Set(['values', 'get', 'pop', 'remove']);
9
9
  const CSHARP_VALUE_METHODS = new Set(['Values', 'TryGetValue']);
10
10
  const SINGLE_ELEMENT_METHODS = new Set([
11
- 'iter', 'into_iter', 'iterator', 'get', 'first', 'last', 'pop',
12
- 'peek', 'poll', 'find', 'filter', 'map',
11
+ 'iter',
12
+ 'into_iter',
13
+ 'iterator',
14
+ 'get',
15
+ 'first',
16
+ 'last',
17
+ 'pop',
18
+ 'peek',
19
+ 'poll',
20
+ 'find',
21
+ 'filter',
22
+ 'map',
13
23
  ]);
14
24
  const CONTAINER_DESCRIPTORS = new Map([
15
25
  // --- Map / Dict types (arity 2: key + value) ---
@@ -22,11 +32,20 @@ const CONTAINER_DESCRIPTORS = new Map([
22
32
  ['dict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
23
33
  ['Dict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
24
34
  ['Dictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
25
- ['SortedDictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
35
+ [
36
+ 'SortedDictionary',
37
+ { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS },
38
+ ],
26
39
  ['Record', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
27
40
  ['OrderedDict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
28
- ['ConcurrentHashMap', { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
29
- ['ConcurrentDictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
41
+ [
42
+ 'ConcurrentHashMap',
43
+ { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS },
44
+ ],
45
+ [
46
+ 'ConcurrentDictionary',
47
+ { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS },
48
+ ],
30
49
  // --- Single-element containers (arity 1) ---
31
50
  ['Array', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
32
51
  ['List', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
@@ -110,8 +129,8 @@ export function getContainerDescriptor(typeName) {
110
129
  */
111
130
  export function resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractFromTypeNode, findParamElementType, typeArgPos = 'last') {
112
131
  // Strategy 1: declarationTypeNodes AST node (check current scope, then file scope)
113
- const typeNode = declarationTypeNodes.get(`${scope}\0${iterableName}`)
114
- ?? (scope !== '' ? declarationTypeNodes.get(`\0${iterableName}`) : undefined);
132
+ const typeNode = declarationTypeNodes.get(`${scope}\0${iterableName}`) ??
133
+ (scope !== '' ? declarationTypeNodes.get(`\0${iterableName}`) : undefined);
115
134
  if (typeNode) {
116
135
  const t = extractFromTypeNode(typeNode, typeArgPos);
117
136
  if (t)
@@ -148,23 +167,32 @@ export const extractSimpleTypeName = (typeNode, depth = 0) => {
148
167
  if (depth > 50 || typeNode.text.length > 2048)
149
168
  return undefined;
150
169
  // Direct type identifier (includes Ruby 'constant' for class names)
151
- if (typeNode.type === 'type_identifier' || typeNode.type === 'identifier'
152
- || typeNode.type === 'simple_identifier' || typeNode.type === 'constant') {
170
+ if (typeNode.type === 'type_identifier' ||
171
+ typeNode.type === 'identifier' ||
172
+ typeNode.type === 'simple_identifier' ||
173
+ typeNode.type === 'constant') {
153
174
  return typeNode.text;
154
175
  }
155
176
  // Qualified/scoped names: take the last segment (e.g., models.User → User, Models::User → User)
156
- if (typeNode.type === 'scoped_identifier' || typeNode.type === 'qualified_identifier'
157
- || typeNode.type === 'scoped_type_identifier' || typeNode.type === 'qualified_name'
158
- || typeNode.type === 'qualified_type'
159
- || typeNode.type === 'member_expression' || typeNode.type === 'member_access_expression'
160
- || typeNode.type === 'attribute'
161
- || typeNode.type === 'scope_resolution'
162
- || typeNode.type === 'selector_expression') {
177
+ if (typeNode.type === 'scoped_identifier' ||
178
+ typeNode.type === 'qualified_identifier' ||
179
+ typeNode.type === 'scoped_type_identifier' ||
180
+ typeNode.type === 'qualified_name' ||
181
+ typeNode.type === 'qualified_type' ||
182
+ typeNode.type === 'member_expression' ||
183
+ typeNode.type === 'member_access_expression' ||
184
+ typeNode.type === 'attribute' ||
185
+ typeNode.type === 'scope_resolution' ||
186
+ typeNode.type === 'selector_expression') {
163
187
  const last = typeNode.lastNamedChild;
164
- if (last && (last.type === 'type_identifier' || last.type === 'identifier'
165
- || last.type === 'simple_identifier' || last.type === 'name'
166
- || last.type === 'constant' || last.type === 'property_identifier'
167
- || last.type === 'field_identifier')) {
188
+ if (last &&
189
+ (last.type === 'type_identifier' ||
190
+ last.type === 'identifier' ||
191
+ last.type === 'simple_identifier' ||
192
+ last.type === 'name' ||
193
+ last.type === 'constant' ||
194
+ last.type === 'property_identifier' ||
195
+ last.type === 'field_identifier')) {
168
196
  return last.text;
169
197
  }
170
198
  }
@@ -176,11 +204,12 @@ export const extractSimpleTypeName = (typeNode, depth = 0) => {
176
204
  }
177
205
  // Generic types: extract the base type (e.g., List<User> → List)
178
206
  // For nullable wrappers (Optional<User>, Option<User>), unwrap to inner type.
179
- if (typeNode.type === 'generic_type' || typeNode.type === 'parameterized_type'
180
- || typeNode.type === 'generic_name') {
181
- const base = typeNode.childForFieldName('name')
182
- ?? typeNode.childForFieldName('type')
183
- ?? typeNode.firstNamedChild;
207
+ if (typeNode.type === 'generic_type' ||
208
+ typeNode.type === 'parameterized_type' ||
209
+ typeNode.type === 'generic_name') {
210
+ const base = typeNode.childForFieldName('name') ??
211
+ typeNode.childForFieldName('type') ??
212
+ typeNode.firstNamedChild;
184
213
  if (!base)
185
214
  return undefined;
186
215
  const baseName = extractSimpleTypeName(base, depth + 1);
@@ -218,8 +247,9 @@ export const extractSimpleTypeName = (typeNode, depth = 0) => {
218
247
  }
219
248
  }
220
249
  // Type annotations that wrap the actual type (TS/Python: `: Foo`, Kotlin: user_type)
221
- if (typeNode.type === 'type_annotation' || typeNode.type === 'type'
222
- || typeNode.type === 'user_type') {
250
+ if (typeNode.type === 'type_annotation' ||
251
+ typeNode.type === 'type' ||
252
+ typeNode.type === 'user_type') {
223
253
  const inner = typeNode.firstNamedChild;
224
254
  if (inner)
225
255
  return extractSimpleTypeName(inner, depth + 1);
@@ -239,9 +269,12 @@ export const extractSimpleTypeName = (typeNode, depth = 0) => {
239
269
  // PHP: primitive_type; TS/JS: predefined_type
240
270
  // Java: integral_type (int/long/short/byte), floating_point_type (float/double),
241
271
  // boolean_type (boolean), void_type (void)
242
- if (typeNode.type === 'primitive_type' || typeNode.type === 'predefined_type'
243
- || typeNode.type === 'integral_type' || typeNode.type === 'floating_point_type'
244
- || typeNode.type === 'boolean_type' || typeNode.type === 'void_type') {
272
+ if (typeNode.type === 'primitive_type' ||
273
+ typeNode.type === 'predefined_type' ||
274
+ typeNode.type === 'integral_type' ||
275
+ typeNode.type === 'floating_point_type' ||
276
+ typeNode.type === 'boolean_type' ||
277
+ typeNode.type === 'void_type') {
245
278
  return typeNode.text;
246
279
  }
247
280
  // PHP named_type / optional_type
@@ -261,9 +294,12 @@ export const extractSimpleTypeName = (typeNode, depth = 0) => {
261
294
  * Returns the simple identifier text, or undefined for destructuring/complex patterns.
262
295
  */
263
296
  export const extractVarName = (node) => {
264
- if (node.type === 'identifier' || node.type === 'simple_identifier'
265
- || node.type === 'variable_name' || node.type === 'name'
266
- || node.type === 'constant' || node.type === 'property_identifier') {
297
+ if (node.type === 'identifier' ||
298
+ node.type === 'simple_identifier' ||
299
+ node.type === 'variable_name' ||
300
+ node.type === 'name' ||
301
+ node.type === 'constant' ||
302
+ node.type === 'property_identifier') {
267
303
  return node.text;
268
304
  }
269
305
  // variable_declarator (Java/C#): has a 'name' field
@@ -320,17 +356,20 @@ export const extractGenericTypeArgs = (typeNode, depth = 0) => {
320
356
  if (depth > 50)
321
357
  return [];
322
358
  // Unwrap wrapper nodes that may sit above the generic_type
323
- if (typeNode.type === 'type_annotation' || typeNode.type === 'type'
324
- || typeNode.type === 'user_type' || typeNode.type === 'nullable_type'
325
- || typeNode.type === 'optional_type') {
359
+ if (typeNode.type === 'type_annotation' ||
360
+ typeNode.type === 'type' ||
361
+ typeNode.type === 'user_type' ||
362
+ typeNode.type === 'nullable_type' ||
363
+ typeNode.type === 'optional_type') {
326
364
  const inner = typeNode.firstNamedChild;
327
365
  if (inner)
328
366
  return extractGenericTypeArgs(inner, depth + 1);
329
367
  return [];
330
368
  }
331
369
  // Only process generic/parameterized type nodes (includes C#'s generic_name)
332
- if (typeNode.type !== 'generic_type' && typeNode.type !== 'parameterized_type'
333
- && typeNode.type !== 'generic_name') {
370
+ if (typeNode.type !== 'generic_type' &&
371
+ typeNode.type !== 'parameterized_type' &&
372
+ typeNode.type !== 'generic_name') {
334
373
  return [];
335
374
  }
336
375
  // Find the type_arguments / type_argument_list child
@@ -438,7 +477,10 @@ export const stripNullable = (typeName) => {
438
477
  text = text.slice(0, -1).trim();
439
478
  // Strip union with null/undefined/None/nil/void
440
479
  if (text.includes('|')) {
441
- const parts = text.split('|').map(p => p.trim()).filter(p => p !== '' && !NULLABLE_KEYWORDS.has(p));
480
+ const parts = text
481
+ .split('|')
482
+ .map((p) => p.trim())
483
+ .filter((p) => p !== '' && !NULLABLE_KEYWORDS.has(p));
442
484
  if (parts.length === 1)
443
485
  return parts[0];
444
486
  return undefined; // genuine union or all-nullable — refuse
@@ -581,10 +623,35 @@ export function extractElementTypeFromString(typeStr, pos = 'last') {
581
623
  // Extracts the base user-defined type name.
582
624
  /** Primitive / built-in types that should NOT produce a receiver binding. */
583
625
  const PRIMITIVE_TYPES = new Set([
584
- 'string', 'number', 'boolean', 'void', 'int', 'float', 'double', 'long',
585
- 'short', 'byte', 'char', 'bool', 'str', 'i8', 'i16', 'i32', 'i64',
586
- 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'usize', 'isize',
587
- 'undefined', 'null', 'None', 'nil',
626
+ 'string',
627
+ 'number',
628
+ 'boolean',
629
+ 'void',
630
+ 'int',
631
+ 'float',
632
+ 'double',
633
+ 'long',
634
+ 'short',
635
+ 'byte',
636
+ 'char',
637
+ 'bool',
638
+ 'str',
639
+ 'i8',
640
+ 'i16',
641
+ 'i32',
642
+ 'i64',
643
+ 'u8',
644
+ 'u16',
645
+ 'u32',
646
+ 'u64',
647
+ 'f32',
648
+ 'f64',
649
+ 'usize',
650
+ 'isize',
651
+ 'undefined',
652
+ 'null',
653
+ 'None',
654
+ 'nil',
588
655
  ]);
589
656
  /**
590
657
  * Extract a simple type name from raw return-type text.
@@ -600,13 +667,27 @@ const PRIMITIVE_TYPES = new Set([
600
667
  * Returns undefined for complex types or primitives.
601
668
  */
602
669
  const WRAPPER_GENERICS = new Set([
603
- 'Promise', 'Observable', 'Future', 'CompletableFuture', 'Task', 'ValueTask', // async wrappers
604
- 'Option', 'Some', 'Optional', 'Maybe', // nullable wrappers
605
- 'Result', 'Either', // result wrappers
670
+ 'Promise',
671
+ 'Observable',
672
+ 'Future',
673
+ 'CompletableFuture',
674
+ 'Task',
675
+ 'ValueTask', // async wrappers
676
+ 'Option',
677
+ 'Some',
678
+ 'Optional',
679
+ 'Maybe', // nullable wrappers
680
+ 'Result',
681
+ 'Either', // result wrappers
606
682
  // Rust smart pointers (Deref to inner type)
607
- 'Rc', 'Arc', 'Weak', // pointer types
608
- 'MutexGuard', 'RwLockReadGuard', 'RwLockWriteGuard', // guard types
609
- 'Ref', 'RefMut', // RefCell guards
683
+ 'Rc',
684
+ 'Arc',
685
+ 'Weak', // pointer types
686
+ 'MutexGuard',
687
+ 'RwLockReadGuard',
688
+ 'RwLockWriteGuard', // guard types
689
+ 'Ref',
690
+ 'RefMut', // RefCell guards
610
691
  'Cow', // copy-on-write
611
692
  // Containers (List, Array, Vec, Set, etc.) are intentionally excluded —
612
693
  // methods are called on the container, not the element type.
@@ -668,7 +749,10 @@ export const extractReturnTypeName = (raw, depth = 0) => {
668
749
  text = text.replace(/\?$/, '');
669
750
  // Handle union types: "User | null" → "User"
670
751
  if (text.includes('|')) {
671
- const parts = text.split('|').map(p => p.trim()).filter(p => p !== 'null' && p !== 'undefined' && p !== 'void' && p !== 'None' && p !== 'nil');
752
+ const parts = text
753
+ .split('|')
754
+ .map((p) => p.trim())
755
+ .filter((p) => p !== 'null' && p !== 'undefined' && p !== 'void' && p !== 'None' && p !== 'nil');
672
756
  if (parts.length === 1)
673
757
  text = parts[0];
674
758
  else