gitnexus 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cli/eval-server.js +13 -5
  2. package/dist/cli/index.js +0 -0
  3. package/dist/cli/tool.d.ts +3 -2
  4. package/dist/cli/tool.js +48 -13
  5. package/dist/core/graph/types.d.ts +2 -2
  6. package/dist/core/ingestion/call-processor.d.ts +7 -2
  7. package/dist/core/ingestion/call-processor.js +308 -235
  8. package/dist/core/ingestion/call-routing.d.ts +17 -2
  9. package/dist/core/ingestion/call-routing.js +21 -0
  10. package/dist/core/ingestion/parsing-processor.d.ts +2 -1
  11. package/dist/core/ingestion/parsing-processor.js +37 -8
  12. package/dist/core/ingestion/pipeline.js +5 -1
  13. package/dist/core/ingestion/symbol-table.d.ts +19 -3
  14. package/dist/core/ingestion/symbol-table.js +41 -2
  15. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
  16. package/dist/core/ingestion/tree-sitter-queries.js +200 -0
  17. package/dist/core/ingestion/type-env.js +126 -18
  18. package/dist/core/ingestion/type-extractors/c-cpp.js +28 -3
  19. package/dist/core/ingestion/type-extractors/csharp.js +61 -7
  20. package/dist/core/ingestion/type-extractors/go.js +86 -10
  21. package/dist/core/ingestion/type-extractors/jvm.js +122 -23
  22. package/dist/core/ingestion/type-extractors/php.js +172 -7
  23. package/dist/core/ingestion/type-extractors/python.js +107 -21
  24. package/dist/core/ingestion/type-extractors/ruby.js +18 -3
  25. package/dist/core/ingestion/type-extractors/rust.js +61 -14
  26. package/dist/core/ingestion/type-extractors/shared.d.ts +13 -0
  27. package/dist/core/ingestion/type-extractors/shared.js +243 -4
  28. package/dist/core/ingestion/type-extractors/types.d.ts +57 -12
  29. package/dist/core/ingestion/type-extractors/typescript.js +52 -8
  30. package/dist/core/ingestion/utils.d.ts +25 -0
  31. package/dist/core/ingestion/utils.js +160 -1
  32. package/dist/core/ingestion/workers/parse-worker.d.ts +23 -7
  33. package/dist/core/ingestion/workers/parse-worker.js +73 -28
  34. package/dist/core/lbug/lbug-adapter.d.ts +2 -0
  35. package/dist/core/lbug/lbug-adapter.js +2 -0
  36. package/dist/core/lbug/schema.d.ts +1 -1
  37. package/dist/core/lbug/schema.js +1 -1
  38. package/dist/mcp/core/lbug-adapter.d.ts +22 -0
  39. package/dist/mcp/core/lbug-adapter.js +167 -23
  40. package/dist/mcp/local/local-backend.d.ts +1 -0
  41. package/dist/mcp/local/local-backend.js +25 -3
  42. package/dist/mcp/resources.js +11 -0
  43. package/dist/mcp/server.js +26 -4
  44. package/dist/mcp/tools.js +15 -5
  45. package/hooks/claude/gitnexus-hook.cjs +0 -0
  46. package/hooks/claude/pre-tool-use.sh +0 -0
  47. package/hooks/claude/session-start.sh +0 -0
  48. package/package.json +6 -5
  49. package/scripts/patch-tree-sitter-swift.cjs +0 -0
@@ -60,6 +60,19 @@ export const TYPESCRIPT_QUERIES = `
60
60
  (new_expression
61
61
  constructor: (identifier) @call.name) @call
62
62
 
63
+ ; Class properties — public_field_definition covers most TS class fields
64
+ (public_field_definition
65
+ name: (property_identifier) @name) @definition.property
66
+
67
+ ; Private class fields: #address: Address
68
+ (public_field_definition
69
+ name: (private_property_identifier) @name) @definition.property
70
+
71
+ ; Constructor parameter properties: constructor(public address: Address)
72
+ (required_parameter
73
+ (accessibility_modifier)
74
+ pattern: (identifier) @name) @definition.property
75
+
63
76
  ; Heritage queries - class extends
64
77
  (class_declaration
65
78
  name: (type_identifier) @heritage.class
@@ -73,6 +86,20 @@ export const TYPESCRIPT_QUERIES = `
73
86
  (class_heritage
74
87
  (implements_clause
75
88
  (type_identifier) @heritage.implements))) @heritage.impl
89
+
90
+ ; Write access: obj.field = value
91
+ (assignment_expression
92
+ left: (member_expression
93
+ object: (_) @assignment.receiver
94
+ property: (property_identifier) @assignment.property)
95
+ right: (_)) @assignment
96
+
97
+ ; Write access: obj.field += value (compound assignment)
98
+ (augmented_assignment_expression
99
+ left: (member_expression
100
+ object: (_) @assignment.receiver
101
+ property: (property_identifier) @assignment.property)
102
+ right: (_)) @assignment
76
103
  `;
77
104
  // JavaScript queries - works with tree-sitter-javascript
78
105
  export const JAVASCRIPT_QUERIES = `
@@ -125,12 +152,30 @@ export const JAVASCRIPT_QUERIES = `
125
152
  (new_expression
126
153
  constructor: (identifier) @call.name) @call
127
154
 
155
+ ; Class fields — field_definition captures JS class fields (class User { address = ... })
156
+ (field_definition
157
+ property: (property_identifier) @name) @definition.property
158
+
128
159
  ; Heritage queries - class extends (JavaScript uses different AST than TypeScript)
129
160
  ; In tree-sitter-javascript, class_heritage directly contains the parent identifier
130
161
  (class_declaration
131
162
  name: (identifier) @heritage.class
132
163
  (class_heritage
133
164
  (identifier) @heritage.extends)) @heritage
165
+
166
+ ; Write access: obj.field = value
167
+ (assignment_expression
168
+ left: (member_expression
169
+ object: (_) @assignment.receiver
170
+ property: (property_identifier) @assignment.property)
171
+ right: (_)) @assignment
172
+
173
+ ; Write access: obj.field += value (compound assignment)
174
+ (augmented_assignment_expression
175
+ left: (member_expression
176
+ object: (_) @assignment.receiver
177
+ property: (property_identifier) @assignment.property)
178
+ right: (_)) @assignment
134
179
  `;
135
180
  // Python queries - works with tree-sitter-python
136
181
  export const PYTHON_QUERIES = `
@@ -156,11 +201,33 @@ export const PYTHON_QUERIES = `
156
201
  function: (attribute
157
202
  attribute: (identifier) @call.name)) @call
158
203
 
204
+ ; Class attribute type annotations — PEP 526: address: Address or address: Address = Address()
205
+ ; Both bare annotations (address: Address) and annotated assignments (name: str = "test")
206
+ ; are parsed as (assignment left: ... type: ...) in tree-sitter-python.
207
+ (expression_statement
208
+ (assignment
209
+ left: (identifier) @name
210
+ type: (type)) @definition.property)
211
+
159
212
  ; Heritage queries - Python class inheritance
160
213
  (class_definition
161
214
  name: (identifier) @heritage.class
162
215
  superclasses: (argument_list
163
216
  (identifier) @heritage.extends)) @heritage
217
+
218
+ ; Write access: obj.field = value
219
+ (assignment
220
+ left: (attribute
221
+ object: (_) @assignment.receiver
222
+ attribute: (identifier) @assignment.property)
223
+ right: (_)) @assignment
224
+
225
+ ; Write access: obj.field += value (compound assignment)
226
+ (augmented_assignment
227
+ left: (attribute
228
+ object: (_) @assignment.receiver
229
+ attribute: (identifier) @assignment.property)
230
+ right: (_)) @assignment
164
231
  `;
165
232
  // Java queries - works with tree-sitter-java
166
233
  export const JAVA_QUERIES = `
@@ -174,6 +241,11 @@ export const JAVA_QUERIES = `
174
241
  (method_declaration name: (identifier) @name) @definition.method
175
242
  (constructor_declaration name: (identifier) @name) @definition.constructor
176
243
 
244
+ ; Fields — typed field declarations inside class bodies
245
+ (field_declaration
246
+ declarator: (variable_declarator
247
+ name: (identifier) @name)) @definition.property
248
+
177
249
  ; Imports - capture any import declaration child as source
178
250
  (import_declaration (_) @import.source) @import
179
251
 
@@ -191,6 +263,13 @@ export const JAVA_QUERIES = `
191
263
  ; Heritage - implements interfaces
192
264
  (class_declaration name: (identifier) @heritage.class
193
265
  (super_interfaces (type_list (type_identifier) @heritage.implements))) @heritage.impl
266
+
267
+ ; Write access: obj.field = value
268
+ (assignment_expression
269
+ left: (field_access
270
+ object: (_) @assignment.receiver
271
+ field: (identifier) @assignment.property)
272
+ right: (_)) @assignment
194
273
  `;
195
274
  // C queries - works with tree-sitter-c
196
275
  export const C_QUERIES = `
@@ -236,6 +315,11 @@ export const GO_QUERIES = `
236
315
  (import_declaration (import_spec path: (interpreted_string_literal) @import.source)) @import
237
316
  (import_declaration (import_spec_list (import_spec path: (interpreted_string_literal) @import.source))) @import
238
317
 
318
+ ; Struct fields — named field declarations inside struct types
319
+ (field_declaration_list
320
+ (field_declaration
321
+ name: (field_identifier) @name) @definition.property)
322
+
239
323
  ; Struct embedding (anonymous fields = inheritance)
240
324
  (type_declaration
241
325
  (type_spec
@@ -251,6 +335,14 @@ export const GO_QUERIES = `
251
335
 
252
336
  ; Struct literal construction: User{Name: "Alice"}
253
337
  (composite_literal type: (type_identifier) @call.name) @call
338
+
339
+ ; Write access: obj.field = value
340
+ (assignment_statement
341
+ left: (expression_list
342
+ (selector_expression
343
+ operand: (_) @assignment.receiver
344
+ field: (field_identifier) @assignment.property))
345
+ right: (_)) @assignment
254
346
  `;
255
347
  // C++ queries - works with tree-sitter-cpp
256
348
  export const CPP_QUERIES = `
@@ -291,6 +383,21 @@ export const CPP_QUERIES = `
291
383
  (declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function
292
384
  (declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function
293
385
 
386
+ ; Class/struct data member fields (Address address; int count;)
387
+ ; Uses field_identifier to exclude method declarations (which use function_declarator)
388
+ (field_declaration
389
+ declarator: (field_identifier) @name) @definition.property
390
+
391
+ ; Pointer member fields (Address* address;)
392
+ (field_declaration
393
+ declarator: (pointer_declarator
394
+ declarator: (field_identifier) @name)) @definition.property
395
+
396
+ ; Reference member fields (Address& address;)
397
+ (field_declaration
398
+ declarator: (reference_declarator
399
+ (field_identifier) @name)) @definition.property
400
+
294
401
  ; Inline class method declarations (inside class body, no body: void Foo();)
295
402
  (field_declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.method
296
403
 
@@ -321,6 +428,14 @@ export const CPP_QUERIES = `
321
428
  (base_class_clause (type_identifier) @heritage.extends)) @heritage
322
429
  (class_specifier name: (type_identifier) @heritage.class
323
430
  (base_class_clause (access_specifier) (type_identifier) @heritage.extends)) @heritage
431
+
432
+ ; Write access: obj.field = value
433
+ (assignment_expression
434
+ left: (field_expression
435
+ argument: (_) @assignment.receiver
436
+ field: (field_identifier) @assignment.property)
437
+ right: (_)) @assignment
438
+
324
439
  `;
325
440
  // C# queries - works with tree-sitter-c-sharp
326
441
  export const CSHARP_QUERIES = `
@@ -374,6 +489,13 @@ export const CSHARP_QUERIES = `
374
489
  (base_list (identifier) @heritage.extends)) @heritage
375
490
  (class_declaration name: (identifier) @heritage.class
376
491
  (base_list (generic_name (identifier) @heritage.extends))) @heritage
492
+
493
+ ; Write access: obj.field = value
494
+ (assignment_expression
495
+ left: (member_access_expression
496
+ expression: (_) @assignment.receiver
497
+ name: (identifier) @assignment.property)
498
+ right: (_)) @assignment
377
499
  `;
378
500
  // Rust queries - works with tree-sitter-rust
379
501
  export const RUST_QUERIES = `
@@ -404,11 +526,30 @@ export const RUST_QUERIES = `
404
526
  ; Struct literal construction: User { name: value }
405
527
  (struct_expression name: (type_identifier) @call.name) @call
406
528
 
529
+ ; Struct fields — named field declarations inside struct bodies
530
+ (field_declaration_list
531
+ (field_declaration
532
+ name: (field_identifier) @name) @definition.property)
533
+
407
534
  ; Heritage (trait implementation) — all combinations of concrete/generic trait × concrete/generic type
408
535
  (impl_item trait: (type_identifier) @heritage.trait type: (type_identifier) @heritage.class) @heritage
409
536
  (impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage
410
537
  (impl_item trait: (type_identifier) @heritage.trait type: (generic_type type: (type_identifier) @heritage.class)) @heritage
411
538
  (impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (generic_type type: (type_identifier) @heritage.class)) @heritage
539
+
540
+ ; Write access: obj.field = value
541
+ (assignment_expression
542
+ left: (field_expression
543
+ value: (_) @assignment.receiver
544
+ field: (field_identifier) @assignment.property)
545
+ right: (_)) @assignment
546
+
547
+ ; Write access: obj.field += value (compound assignment)
548
+ (compound_assignment_expr
549
+ left: (field_expression
550
+ value: (_) @assignment.receiver
551
+ field: (field_identifier) @assignment.property)
552
+ right: (_)) @assignment
412
553
  `;
413
554
  // PHP queries - works with tree-sitter-php (php_only grammar)
414
555
  export const PHP_QUERIES = `
@@ -446,6 +587,13 @@ export const PHP_QUERIES = `
446
587
  (variable_name
447
588
  (name) @name))) @definition.property
448
589
 
590
+ ; Constructor property promotion (PHP 8.0+: public Address $address in __construct)
591
+ (method_declaration
592
+ parameters: (formal_parameters
593
+ (property_promotion_parameter
594
+ name: (variable_name
595
+ (name) @name)))) @definition.property
596
+
449
597
  ; ── Imports: use statements ──────────────────────────────────────────────────
450
598
  ; Simple: use App\\Models\\User;
451
599
  (namespace_use_declaration
@@ -490,6 +638,20 @@ export const PHP_QUERIES = `
490
638
  body: (declaration_list
491
639
  (use_declaration
492
640
  [(name) (qualified_name)] @heritage.trait))) @heritage
641
+
642
+ ; Write access: $obj->field = value
643
+ (assignment_expression
644
+ left: (member_access_expression
645
+ object: (_) @assignment.receiver
646
+ name: (name) @assignment.property)
647
+ right: (_)) @assignment
648
+
649
+ ; Write access: ClassName::$field = value (static property)
650
+ (assignment_expression
651
+ left: (scoped_property_access_expression
652
+ scope: (_) @assignment.receiver
653
+ name: (variable_name (name) @assignment.property))
654
+ right: (_)) @assignment
493
655
  `;
494
656
  // Ruby queries - works with tree-sitter-ruby
495
657
  // NOTE: Ruby uses `call` for require, include, extend, prepend, attr_* etc.
@@ -534,6 +696,20 @@ export const RUBY_QUERIES = `
534
696
  name: (constant) @heritage.class
535
697
  superclass: (superclass
536
698
  (constant) @heritage.extends)) @heritage
699
+
700
+ ; Write access: obj.field = value (Ruby setter — syntactically a method call to field=)
701
+ (assignment
702
+ left: (call
703
+ receiver: (_) @assignment.receiver
704
+ method: (identifier) @assignment.property)
705
+ right: (_)) @assignment
706
+
707
+ ; Write access: obj.field += value (compound assignment — operator_assignment node, not assignment)
708
+ (operator_assignment
709
+ left: (call
710
+ receiver: (_) @assignment.receiver
711
+ method: (identifier) @assignment.property)
712
+ right: (_)) @assignment
537
713
  `;
538
714
  // Kotlin queries - works with tree-sitter-kotlin (fwcd/tree-sitter-kotlin)
539
715
  // Based on official tags.scm; functions use simple_identifier, classes use type_identifier
@@ -569,6 +745,12 @@ export const KOTLIN_QUERIES = `
569
745
  (variable_declaration
570
746
  (simple_identifier) @name)) @definition.property
571
747
 
748
+ ; Primary constructor val/var parameters (data class, value class, regular class)
749
+ ; binding_pattern_kind contains "val" or "var" — without it, the param is not a property
750
+ (class_parameter
751
+ (binding_pattern_kind)
752
+ (simple_identifier) @name) @definition.property
753
+
572
754
  ; ── Enum entries ─────────────────────────────────────────────────────────
573
755
  (enum_entry
574
756
  (simple_identifier) @name) @definition.enum
@@ -613,6 +795,15 @@ export const KOTLIN_QUERIES = `
613
795
  (delegation_specifier
614
796
  (constructor_invocation
615
797
  (user_type (type_identifier) @heritage.extends)))) @heritage
798
+
799
+ ; Write access: obj.field = value
800
+ (assignment
801
+ (directly_assignable_expression
802
+ (_) @assignment.receiver
803
+ (navigation_suffix
804
+ (simple_identifier) @assignment.property))
805
+ (_)) @assignment
806
+
616
807
  `;
617
808
  // Swift queries - works with tree-sitter-swift
618
809
  export const SWIFT_QUERIES = `
@@ -670,6 +861,15 @@ export const SWIFT_QUERIES = `
670
861
  ; Extensions wrap the name in user_type unlike class/struct/enum declarations
671
862
  (class_declaration "extension" name: (user_type (type_identifier) @heritage.class)
672
863
  (inheritance_specifier inherits_from: (user_type (type_identifier) @heritage.extends))) @heritage
864
+
865
+ ; Write access: obj.field = value
866
+ (assignment
867
+ (directly_assignable_expression
868
+ (_) @assignment.receiver
869
+ (navigation_suffix
870
+ (simple_identifier) @assignment.property))
871
+ (_)) @assignment
872
+
673
873
  `;
674
874
  export const LANGUAGE_QUERIES = {
675
875
  [SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES,
@@ -1,6 +1,6 @@
1
- import { FUNCTION_NODE_TYPES, extractFunctionName, CLASS_CONTAINER_TYPES } from './utils.js';
1
+ import { FUNCTION_NODE_TYPES, extractFunctionName, CLASS_CONTAINER_TYPES, isBuiltInOrNoise } from './utils.js';
2
2
  import { typeConfigs, TYPED_PARAMETER_TYPES } from './type-extractors/index.js';
3
- import { extractSimpleTypeName, extractVarName, stripNullable } from './type-extractors/shared.js';
3
+ import { extractSimpleTypeName, extractVarName, stripNullable, extractReturnTypeName } from './type-extractors/shared.js';
4
4
  /** File-level scope key */
5
5
  const FILE_SCOPE = '';
6
6
  /** Fallback for languages where class names aren't in a 'name' field (e.g. Kotlin uses type_identifier). */
@@ -296,6 +296,47 @@ const SKIP_SUBTREE_TYPES = new Set([
296
296
  // Regex
297
297
  'regex', 'regex_pattern',
298
298
  ]);
299
+ const CLASS_LIKE_TYPES = new Set(['Class', 'Struct', 'Interface']);
300
+ /** Resolve a field's declared type given a receiver variable and field name.
301
+ * Uses SymbolTable to find the class nodeId for the receiver's type, then
302
+ * looks up the field via the eagerly-populated fieldByOwner index. */
303
+ const resolveFieldType = (receiver, field, scopeEnv, symbolTable) => {
304
+ if (!symbolTable)
305
+ return undefined;
306
+ const receiverType = scopeEnv.get(receiver);
307
+ if (!receiverType)
308
+ return undefined;
309
+ const classDefs = symbolTable.lookupFuzzy(receiverType)
310
+ .filter(d => CLASS_LIKE_TYPES.has(d.type));
311
+ if (classDefs.length !== 1)
312
+ return undefined;
313
+ const fieldDef = symbolTable.lookupFieldByOwner(classDefs[0].nodeId, field);
314
+ if (!fieldDef?.declaredType)
315
+ return undefined;
316
+ return extractReturnTypeName(fieldDef.declaredType);
317
+ };
318
+ /** Resolve a method's return type given a receiver variable and method name.
319
+ * Uses SymbolTable to find class nodeIds for the receiver's type, then
320
+ * looks up the method via lookupFuzzyCallable filtered by ownerId. */
321
+ const resolveMethodReturnType = (receiver, method, scopeEnv, symbolTable) => {
322
+ if (!symbolTable)
323
+ return undefined;
324
+ const receiverType = scopeEnv.get(receiver);
325
+ if (!receiverType)
326
+ return undefined;
327
+ const classDefs = symbolTable.lookupFuzzy(receiverType)
328
+ .filter(d => CLASS_LIKE_TYPES.has(d.type));
329
+ if (classDefs.length === 0)
330
+ return undefined;
331
+ const classNodeIds = new Set(classDefs.map(d => d.nodeId));
332
+ const methods = symbolTable.lookupFuzzyCallable(method)
333
+ .filter(d => d.ownerId && classNodeIds.has(d.ownerId));
334
+ if (methods.length !== 1)
335
+ return undefined;
336
+ if (!methods[0].returnType)
337
+ return undefined;
338
+ return extractReturnTypeName(methods[0].returnType);
339
+ };
299
340
  export const buildTypeEnv = (tree, language, symbolTable) => {
300
341
  const env = new Map();
301
342
  const patternOverrides = new Map();
@@ -303,13 +344,43 @@ export const buildTypeEnv = (tree, language, symbolTable) => {
303
344
  const classNames = createClassNameLookup(localClassNames, symbolTable);
304
345
  const config = typeConfigs[language];
305
346
  const bindings = [];
347
+ // Build ReturnTypeLookup from optional SymbolTable.
348
+ // Conservative: returns undefined when callee is ambiguous (0 or 2+ matches).
349
+ const returnTypeLookup = {
350
+ lookupReturnType(callee) {
351
+ if (!symbolTable)
352
+ return undefined;
353
+ if (isBuiltInOrNoise(callee))
354
+ return undefined;
355
+ const callables = symbolTable.lookupFuzzyCallable(callee);
356
+ if (callables.length !== 1)
357
+ return undefined;
358
+ const rawReturn = callables[0].returnType;
359
+ if (!rawReturn)
360
+ return undefined;
361
+ return extractReturnTypeName(rawReturn);
362
+ },
363
+ lookupRawReturnType(callee) {
364
+ if (!symbolTable)
365
+ return undefined;
366
+ if (isBuiltInOrNoise(callee))
367
+ return undefined;
368
+ const callables = symbolTable.lookupFuzzyCallable(callee);
369
+ if (callables.length !== 1)
370
+ return undefined;
371
+ return callables[0].returnType;
372
+ }
373
+ };
306
374
  // Pre-compute combined set of node types that need extractTypeBinding.
307
375
  // Single Set.has() replaces 3 separate checks per node in walk().
308
376
  const interestingNodeTypes = new Set();
309
377
  TYPED_PARAMETER_TYPES.forEach(t => interestingNodeTypes.add(t));
310
378
  config.declarationNodeTypes.forEach(t => interestingNodeTypes.add(t));
311
379
  config.forLoopNodeTypes?.forEach(t => interestingNodeTypes.add(t));
312
- const pendingAssignments = [];
380
+ // Tier 2: unified fixpoint propagation — collects copy, callResult, fieldAccess, and
381
+ // methodCallResult items during walk(), then iterates until no new bindings are produced.
382
+ // Handles arbitrary-depth mixed chains: callResult → fieldAccess → methodCallResult → copy.
383
+ const pendingItems = [];
313
384
  // Maps `scope\0varName` → the type annotation AST node from the original declaration.
314
385
  // Allows pattern extractors to navigate back to the declaration's generic type arguments
315
386
  // (e.g., to extract T from Result<T, E> for `if let Ok(x) = res`).
@@ -339,7 +410,9 @@ export const buildTypeEnv = (tree, language, symbolTable) => {
339
410
  let typeNode = node.childForFieldName('type');
340
411
  if (typeNode) {
341
412
  const nameNode = node.childForFieldName('name')
342
- ?? node.childForFieldName('pattern');
413
+ ?? node.childForFieldName('pattern')
414
+ // Python typed_parameter: name is a positional child (identifier), not a named field
415
+ ?? (node.firstNamedChild?.type === 'identifier' ? node.firstNamedChild : null);
343
416
  if (nameNode) {
344
417
  const varName = extractVarName(nameNode);
345
418
  if (varName && !declarationTypeNodes.has(`${scope}\0${varName}`)) {
@@ -376,7 +449,10 @@ export const buildTypeEnv = (tree, language, symbolTable) => {
376
449
  // For-each loop variable bindings (Java/C#/Kotlin): explicit element types in the AST.
377
450
  // Checked before declarationNodeTypes — loop variables are not declarations.
378
451
  if (config.forLoopNodeTypes?.has(node.type)) {
379
- config.extractForLoopBinding?.(node, scopeEnv, declarationTypeNodes, scope);
452
+ if (config.extractForLoopBinding) {
453
+ const forLoopCtx = { scopeEnv, declarationTypeNodes, scope, returnTypeLookup };
454
+ config.extractForLoopBinding(node, forLoopCtx);
455
+ }
380
456
  return;
381
457
  }
382
458
  if (config.declarationNodeTypes.has(node.type)) {
@@ -514,7 +590,7 @@ export const buildTypeEnv = (tree, language, symbolTable) => {
514
590
  if (scopeEnv) {
515
591
  const pending = config.extractPendingAssignment(node, scopeEnv);
516
592
  if (pending) {
517
- pendingAssignments.push({ scope, ...pending });
593
+ pendingItems.push({ scope, ...pending });
518
594
  }
519
595
  }
520
596
  }
@@ -537,19 +613,51 @@ export const buildTypeEnv = (tree, language, symbolTable) => {
537
613
  }
538
614
  };
539
615
  walk(tree.rootNode, FILE_SCOPE);
540
- // Tier 2: single-pass assignment chain propagation in source order.
541
- // Resolves `const b = a` where `a` has a known type from Tier 0/1.
542
- // Multi-hop chains resolve when forward-declared (a→b→c in source order);
543
- // reverse-order assignments are depth-1 only. No fixpoint iteration —
544
- // this covers 95%+ of real-world patterns.
545
- for (const { scope, lhs, rhs } of pendingAssignments) {
546
- const scopeEnv = env.get(scope);
547
- if (!scopeEnv || scopeEnv.has(lhs))
548
- continue;
549
- const rhsType = scopeEnv.get(rhs) ?? env.get(FILE_SCOPE)?.get(rhs);
550
- if (rhsType) {
551
- scopeEnv.set(lhs, rhsType);
616
+ // Unified fixpoint propagation: iterate over ALL pending items (copy, callResult,
617
+ // fieldAccess, methodCallResult) until no new bindings are produced.
618
+ // Handles arbitrary-depth mixed chains:
619
+ // const user = getUser(); // callResult User
620
+ // const addr = user.address; // fieldAccess Address (depends on user)
621
+ // const city = addr.getCity(); // methodCallResult City (depends on addr)
622
+ // const alias = city; // copy → City (depends on city)
623
+ // Data flow: SymbolTable (immutable) + scopeEnv resolve → scopeEnv.
624
+ // Termination: finite entries, each bound at most once (first-writer-wins), max 10 iterations.
625
+ const MAX_FIXPOINT_ITERATIONS = 10;
626
+ const resolved = new Set();
627
+ for (let iter = 0; iter < MAX_FIXPOINT_ITERATIONS; iter++) {
628
+ let changed = false;
629
+ for (let i = 0; i < pendingItems.length; i++) {
630
+ if (resolved.has(i))
631
+ continue;
632
+ const item = pendingItems[i];
633
+ const scopeEnv = env.get(item.scope);
634
+ if (!scopeEnv || scopeEnv.has(item.lhs)) {
635
+ resolved.add(i);
636
+ continue;
637
+ }
638
+ let typeName;
639
+ switch (item.kind) {
640
+ case 'callResult':
641
+ typeName = returnTypeLookup.lookupReturnType(item.callee);
642
+ break;
643
+ case 'copy':
644
+ typeName = scopeEnv.get(item.rhs) ?? env.get(FILE_SCOPE)?.get(item.rhs);
645
+ break;
646
+ case 'fieldAccess':
647
+ typeName = resolveFieldType(item.receiver, item.field, scopeEnv, symbolTable);
648
+ break;
649
+ case 'methodCallResult':
650
+ typeName = resolveMethodReturnType(item.receiver, item.method, scopeEnv, symbolTable);
651
+ break;
652
+ }
653
+ if (typeName) {
654
+ scopeEnv.set(item.lhs, typeName);
655
+ resolved.add(i);
656
+ changed = true;
657
+ }
552
658
  }
659
+ if (!changed)
660
+ break;
553
661
  }
554
662
  return {
555
663
  lookup: (varName, callNode) => lookupInEnv(env, varName, callNode, patternOverrides),
@@ -186,7 +186,7 @@ const extractPendingAssignment = (node, scopeEnv) => {
186
186
  if (!declarator || declarator.type !== 'init_declarator')
187
187
  return undefined;
188
188
  const value = declarator.childForFieldName('value');
189
- if (!value || value.type !== 'identifier')
189
+ if (!value)
190
190
  return undefined;
191
191
  const nameNode = declarator.childForFieldName('declarator');
192
192
  if (!nameNode)
@@ -198,7 +198,32 @@ const extractPendingAssignment = (node, scopeEnv) => {
198
198
  const lhs = extractVarName(finalName);
199
199
  if (!lhs || scopeEnv.has(lhs))
200
200
  return undefined;
201
- return { lhs, rhs: value.text };
201
+ if (value.type === 'identifier')
202
+ return { kind: 'copy', lhs, rhs: value.text };
203
+ // field_expression RHS → fieldAccess (a.field)
204
+ if (value.type === 'field_expression') {
205
+ const obj = value.firstNamedChild;
206
+ const field = value.lastNamedChild;
207
+ if (obj?.type === 'identifier' && field?.type === 'field_identifier') {
208
+ return { kind: 'fieldAccess', lhs, receiver: obj.text, field: field.text };
209
+ }
210
+ }
211
+ // call_expression RHS
212
+ if (value.type === 'call_expression') {
213
+ const funcNode = value.childForFieldName('function');
214
+ if (funcNode?.type === 'identifier') {
215
+ return { kind: 'callResult', lhs, callee: funcNode.text };
216
+ }
217
+ // method call with receiver: call_expression → function: field_expression
218
+ if (funcNode?.type === 'field_expression') {
219
+ const obj = funcNode.firstNamedChild;
220
+ const field = funcNode.lastNamedChild;
221
+ if (obj?.type === 'identifier' && field?.type === 'field_identifier') {
222
+ return { kind: 'methodCallResult', lhs, receiver: obj.text, method: field.text };
223
+ }
224
+ }
225
+ }
226
+ return undefined;
202
227
  };
203
228
  // --- For-loop Tier 1c ---
204
229
  const FOR_LOOP_NODE_TYPES = new Set(['for_range_loop']);
@@ -291,7 +316,7 @@ const findCppParamElementType = (iterableName, startNode, pos = 'last') => {
291
316
  /** C++: for (auto& user : users) — extract loop variable binding.
292
317
  * Handles explicit types (for (User& user : users)) and auto (for (auto& user : users)).
293
318
  * For auto, resolves element type from the iterable's container type. */
294
- const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
319
+ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope }) => {
295
320
  if (node.type !== 'for_range_loop')
296
321
  return;
297
322
  const typeNode = node.childForFieldName('type');