@veewo/gitnexus 1.3.11 → 1.4.6-rc

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 (181) hide show
  1. package/README.md +37 -80
  2. package/dist/benchmark/agent-context/tool-runner.js +2 -2
  3. package/dist/benchmark/neonspark-candidates.js +3 -3
  4. package/dist/benchmark/tool-runner.js +2 -2
  5. package/dist/cli/ai-context.d.ts +2 -1
  6. package/dist/cli/ai-context.js +16 -12
  7. package/dist/cli/analyze.d.ts +2 -0
  8. package/dist/cli/analyze.js +68 -48
  9. package/dist/cli/augment.js +1 -1
  10. package/dist/cli/eval-server.d.ts +8 -1
  11. package/dist/cli/eval-server.js +30 -13
  12. package/dist/cli/index.js +28 -82
  13. package/dist/cli/lazy-action.d.ts +6 -0
  14. package/dist/cli/lazy-action.js +18 -0
  15. package/dist/cli/mcp.js +3 -1
  16. package/dist/cli/setup.js +87 -48
  17. package/dist/cli/setup.test.js +18 -13
  18. package/dist/cli/skill-gen.d.ts +26 -0
  19. package/dist/cli/skill-gen.js +549 -0
  20. package/dist/cli/status.js +13 -4
  21. package/dist/cli/tool.d.ts +3 -2
  22. package/dist/cli/tool.js +50 -16
  23. package/dist/cli/wiki.js +8 -4
  24. package/dist/config/ignore-service.d.ts +25 -0
  25. package/dist/config/ignore-service.js +76 -0
  26. package/dist/config/supported-languages.d.ts +4 -1
  27. package/dist/config/supported-languages.js +3 -2
  28. package/dist/core/augmentation/engine.js +94 -67
  29. package/dist/core/embeddings/embedder.d.ts +1 -1
  30. package/dist/core/embeddings/embedder.js +1 -1
  31. package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
  32. package/dist/core/embeddings/embedding-pipeline.js +52 -25
  33. package/dist/core/embeddings/types.d.ts +1 -1
  34. package/dist/core/graph/types.d.ts +7 -2
  35. package/dist/core/ingestion/ast-cache.js +3 -2
  36. package/dist/core/ingestion/call-processor.d.ts +8 -6
  37. package/dist/core/ingestion/call-processor.js +468 -206
  38. package/dist/core/ingestion/call-routing.d.ts +53 -0
  39. package/dist/core/ingestion/call-routing.js +108 -0
  40. package/dist/core/ingestion/constants.d.ts +16 -0
  41. package/dist/core/ingestion/constants.js +16 -0
  42. package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
  43. package/dist/core/ingestion/entry-point-scoring.js +116 -23
  44. package/dist/core/ingestion/export-detection.d.ts +18 -0
  45. package/dist/core/ingestion/export-detection.js +231 -0
  46. package/dist/core/ingestion/filesystem-walker.js +4 -3
  47. package/dist/core/ingestion/framework-detection.d.ts +19 -4
  48. package/dist/core/ingestion/framework-detection.js +182 -6
  49. package/dist/core/ingestion/heritage-processor.d.ts +13 -5
  50. package/dist/core/ingestion/heritage-processor.js +109 -55
  51. package/dist/core/ingestion/import-processor.d.ts +16 -20
  52. package/dist/core/ingestion/import-processor.js +199 -579
  53. package/dist/core/ingestion/language-config.d.ts +46 -0
  54. package/dist/core/ingestion/language-config.js +167 -0
  55. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  56. package/dist/core/ingestion/mro-processor.js +369 -0
  57. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  58. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  59. package/dist/core/ingestion/parsing-processor.d.ts +4 -1
  60. package/dist/core/ingestion/parsing-processor.js +107 -109
  61. package/dist/core/ingestion/pipeline.d.ts +6 -3
  62. package/dist/core/ingestion/pipeline.js +208 -114
  63. package/dist/core/ingestion/process-processor.js +8 -2
  64. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  65. package/dist/core/ingestion/resolution-context.js +132 -0
  66. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  67. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  68. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  69. package/dist/core/ingestion/resolvers/go.js +42 -0
  70. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  71. package/dist/core/ingestion/resolvers/index.js +13 -0
  72. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  73. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  74. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  75. package/dist/core/ingestion/resolvers/php.js +35 -0
  76. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  77. package/dist/core/ingestion/resolvers/python.js +52 -0
  78. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  79. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  80. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  81. package/dist/core/ingestion/resolvers/rust.js +73 -0
  82. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  83. package/dist/core/ingestion/resolvers/standard.js +123 -0
  84. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  85. package/dist/core/ingestion/resolvers/utils.js +122 -0
  86. package/dist/core/ingestion/symbol-table.d.ts +21 -1
  87. package/dist/core/ingestion/symbol-table.js +40 -12
  88. package/dist/core/ingestion/tree-sitter-queries.d.ts +13 -10
  89. package/dist/core/ingestion/tree-sitter-queries.js +297 -7
  90. package/dist/core/ingestion/type-env.d.ts +49 -0
  91. package/dist/core/ingestion/type-env.js +611 -0
  92. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  93. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  94. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  95. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  96. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  97. package/dist/core/ingestion/type-extractors/go.js +467 -0
  98. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  99. package/dist/core/ingestion/type-extractors/index.js +31 -0
  100. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  101. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  102. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  103. package/dist/core/ingestion/type-extractors/php.js +549 -0
  104. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  105. package/dist/core/ingestion/type-extractors/python.js +406 -0
  106. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  107. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  108. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  109. package/dist/core/ingestion/type-extractors/rust.js +449 -0
  110. package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
  111. package/dist/core/ingestion/type-extractors/shared.js +703 -0
  112. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  113. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  114. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  115. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  116. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  117. package/dist/core/ingestion/utils.d.ts +103 -0
  118. package/dist/core/ingestion/utils.js +1085 -4
  119. package/dist/core/ingestion/workers/parse-worker.d.ts +51 -4
  120. package/dist/core/ingestion/workers/parse-worker.js +634 -222
  121. package/dist/core/ingestion/workers/worker-pool.js +8 -0
  122. package/dist/core/{kuzu → lbug}/csv-generator.d.ts +12 -10
  123. package/dist/core/{kuzu → lbug}/csv-generator.js +82 -101
  124. package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +20 -25
  125. package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +150 -122
  126. package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
  127. package/dist/core/{kuzu → lbug}/schema.js +23 -22
  128. package/dist/core/lbug/schema.test.d.ts +1 -0
  129. package/dist/core/search/bm25-index.d.ts +4 -4
  130. package/dist/core/search/bm25-index.js +12 -11
  131. package/dist/core/search/hybrid-search.d.ts +2 -2
  132. package/dist/core/search/hybrid-search.js +6 -6
  133. package/dist/core/tree-sitter/parser-loader.d.ts +1 -0
  134. package/dist/core/tree-sitter/parser-loader.js +19 -0
  135. package/dist/core/wiki/generator.d.ts +2 -2
  136. package/dist/core/wiki/generator.js +6 -6
  137. package/dist/core/wiki/graph-queries.d.ts +4 -4
  138. package/dist/core/wiki/graph-queries.js +7 -7
  139. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  140. package/dist/mcp/compatible-stdio-transport.js +200 -0
  141. package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +11 -10
  142. package/dist/mcp/core/lbug-adapter.js +327 -0
  143. package/dist/mcp/local/local-backend.d.ts +21 -16
  144. package/dist/mcp/local/local-backend.js +306 -706
  145. package/dist/mcp/local/unity-parity-seed-loader.d.ts +6 -1
  146. package/dist/mcp/local/unity-parity-seed-loader.js +119 -9
  147. package/dist/mcp/local/unity-parity-seed-loader.test.js +95 -7
  148. package/dist/mcp/resources.js +2 -2
  149. package/dist/mcp/server.js +28 -13
  150. package/dist/mcp/staleness.js +2 -2
  151. package/dist/mcp/tools.js +12 -3
  152. package/dist/server/api.js +12 -12
  153. package/dist/server/mcp-http.d.ts +1 -1
  154. package/dist/server/mcp-http.js +1 -1
  155. package/dist/storage/git.js +4 -1
  156. package/dist/storage/repo-manager.d.ts +20 -2
  157. package/dist/storage/repo-manager.js +74 -4
  158. package/dist/types/pipeline.d.ts +1 -1
  159. package/hooks/claude/gitnexus-hook.cjs +149 -46
  160. package/hooks/claude/pre-tool-use.sh +2 -1
  161. package/hooks/claude/session-start.sh +0 -0
  162. package/package.json +20 -4
  163. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  164. package/skills/gitnexus-cli.md +8 -8
  165. package/skills/gitnexus-debugging.md +1 -1
  166. package/skills/gitnexus-exploring.md +1 -1
  167. package/skills/gitnexus-guide.md +1 -1
  168. package/skills/gitnexus-impact-analysis.md +1 -1
  169. package/skills/gitnexus-pr-review.md +163 -0
  170. package/skills/gitnexus-refactoring.md +1 -1
  171. package/dist/cli/claude-hooks.d.ts +0 -22
  172. package/dist/cli/claude-hooks.js +0 -97
  173. package/dist/mcp/core/kuzu-adapter.js +0 -231
  174. /package/dist/core/{kuzu/csv-generator.test.d.ts → ingestion/type-extractors/types.js} +0 -0
  175. /package/dist/core/{kuzu/relationship-pair-buckets.test.d.ts → lbug/csv-generator.test.d.ts} +0 -0
  176. /package/dist/core/{kuzu → lbug}/csv-generator.test.js +0 -0
  177. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.d.ts +0 -0
  178. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.js +0 -0
  179. /package/dist/core/{kuzu/schema.test.d.ts → lbug/relationship-pair-buckets.test.d.ts} +0 -0
  180. /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.test.js +0 -0
  181. /package/dist/core/{kuzu → lbug}/schema.test.js +0 -0
@@ -0,0 +1,406 @@
1
+ import { extractSimpleTypeName, extractVarName, extractElementTypeFromString, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition } from './shared.js';
2
+ const DECLARATION_NODE_TYPES = new Set([
3
+ 'assignment',
4
+ 'named_expression',
5
+ 'expression_statement',
6
+ ]);
7
+ /** Python: x: Foo = ... (PEP 484 annotated assignment) or x: Foo (standalone annotation).
8
+ *
9
+ * tree-sitter-python grammar produces two distinct shapes:
10
+ *
11
+ * 1. Annotated assignment with value: `name: str = ""`
12
+ * Node type: `assignment`
13
+ * Fields: left=identifier, type=identifier/type, right=value
14
+ *
15
+ * 2. Standalone annotation (no value): `name: str`
16
+ * Node type: `expression_statement`
17
+ * Child: `type` node with fields name=identifier, type=identifier/type
18
+ *
19
+ * Both appear at file scope and inside class bodies (PEP 526 class variable annotations).
20
+ */
21
+ const extractDeclaration = (node, env) => {
22
+ if (node.type === 'expression_statement') {
23
+ // Standalone annotation: expression_statement > type { name: identifier, type: identifier }
24
+ const typeChild = node.firstNamedChild;
25
+ if (!typeChild || typeChild.type !== 'type')
26
+ return;
27
+ const nameNode = typeChild.childForFieldName('name');
28
+ const typeNode = typeChild.childForFieldName('type');
29
+ if (!nameNode || !typeNode)
30
+ return;
31
+ const varName = extractVarName(nameNode);
32
+ const inner = typeNode.type === 'type' ? (typeNode.firstNamedChild ?? typeNode) : typeNode;
33
+ const typeName = extractSimpleTypeName(inner) ?? inner.text;
34
+ if (varName && typeName)
35
+ env.set(varName, typeName);
36
+ return;
37
+ }
38
+ // Annotated assignment: left : type = value
39
+ const left = node.childForFieldName('left');
40
+ const typeNode = node.childForFieldName('type');
41
+ if (!left || !typeNode)
42
+ return;
43
+ const varName = extractVarName(left);
44
+ // extractSimpleTypeName handles identifiers and qualified names.
45
+ // Python 3.10+ union syntax `User | None` is parsed as binary_operator,
46
+ // which extractSimpleTypeName doesn't handle. Fall back to raw text so
47
+ // stripNullable can process it at lookup time (e.g., "User | None" → "User").
48
+ const inner = typeNode.type === 'type' ? (typeNode.firstNamedChild ?? typeNode) : typeNode;
49
+ const typeName = extractSimpleTypeName(inner) ?? inner.text;
50
+ if (varName && typeName)
51
+ env.set(varName, typeName);
52
+ };
53
+ /** Python: parameter with type annotation */
54
+ const extractParameter = (node, env) => {
55
+ let nameNode = null;
56
+ let typeNode = null;
57
+ if (node.type === 'parameter') {
58
+ nameNode = node.childForFieldName('name');
59
+ typeNode = node.childForFieldName('type');
60
+ }
61
+ else {
62
+ nameNode = node.childForFieldName('name') ?? node.childForFieldName('pattern');
63
+ typeNode = node.childForFieldName('type');
64
+ }
65
+ if (!nameNode || !typeNode)
66
+ return;
67
+ const varName = extractVarName(nameNode);
68
+ const typeName = extractSimpleTypeName(typeNode);
69
+ if (varName && typeName)
70
+ env.set(varName, typeName);
71
+ };
72
+ /** Python: user = User("alice") — infer type from call when callee is a known class.
73
+ * Python constructors are syntactically identical to function calls, so we verify
74
+ * against classNames (which may include cross-file SymbolTable lookups).
75
+ * Also handles walrus operator: if (user := User("alice")): */
76
+ const extractInitializer = (node, env, classNames) => {
77
+ let left;
78
+ let right;
79
+ if (node.type === 'named_expression') {
80
+ // Walrus operator: (user := User("alice"))
81
+ // tree-sitter-python: named_expression has 'name' and 'value' fields
82
+ left = node.childForFieldName('name');
83
+ right = node.childForFieldName('value');
84
+ }
85
+ else if (node.type === 'assignment') {
86
+ left = node.childForFieldName('left');
87
+ right = node.childForFieldName('right');
88
+ // Skip if already has type annotation — extractDeclaration handled it
89
+ if (node.childForFieldName('type'))
90
+ return;
91
+ }
92
+ else {
93
+ return;
94
+ }
95
+ if (!left || !right)
96
+ return;
97
+ const varName = extractVarName(left);
98
+ if (!varName || env.has(varName))
99
+ return;
100
+ if (right.type !== 'call')
101
+ return;
102
+ const func = right.childForFieldName('function');
103
+ if (!func)
104
+ return;
105
+ // Support both direct calls (User()) and qualified calls (models.User())
106
+ // tree-sitter-python: direct → identifier, qualified → attribute
107
+ const calleeName = extractSimpleTypeName(func);
108
+ if (!calleeName)
109
+ return;
110
+ if (classNames.has(calleeName)) {
111
+ env.set(varName, calleeName);
112
+ }
113
+ };
114
+ /** Python: user = User("alice") — scan assignment/walrus for constructor-like calls.
115
+ * Returns {varName, calleeName} without checking classNames (caller validates). */
116
+ const scanConstructorBinding = (node) => {
117
+ let left;
118
+ let right;
119
+ if (node.type === 'named_expression') {
120
+ left = node.childForFieldName('name');
121
+ right = node.childForFieldName('value');
122
+ }
123
+ else if (node.type === 'assignment') {
124
+ left = node.childForFieldName('left');
125
+ right = node.childForFieldName('right');
126
+ if (node.childForFieldName('type'))
127
+ return undefined;
128
+ }
129
+ else {
130
+ return undefined;
131
+ }
132
+ if (!left || !right)
133
+ return undefined;
134
+ if (left.type !== 'identifier')
135
+ return undefined;
136
+ if (right.type !== 'call')
137
+ return undefined;
138
+ const func = right.childForFieldName('function');
139
+ if (!func)
140
+ return undefined;
141
+ const calleeName = extractSimpleTypeName(func);
142
+ if (!calleeName)
143
+ return undefined;
144
+ return { varName: left.text, calleeName };
145
+ };
146
+ const FOR_LOOP_NODE_TYPES = new Set([
147
+ 'for_statement',
148
+ ]);
149
+ /** Python function/method node types that carry a parameters list. */
150
+ const PY_FUNCTION_NODE_TYPES = new Set([
151
+ 'function_definition', 'decorated_definition',
152
+ ]);
153
+ /**
154
+ * Extract element type from a Python type annotation AST node.
155
+ * Handles:
156
+ * subscript "List[User]" → extractElementTypeFromString("List[User]") → "User"
157
+ * generic_type → extractGenericTypeArgs → first arg
158
+ * Falls back to text-based extraction.
159
+ */
160
+ const extractPyElementTypeFromAnnotation = (typeNode, pos = 'last') => {
161
+ // Unwrap 'type' wrapper node to get to the actual type (e.g., type > generic_type)
162
+ const inner = typeNode.type === 'type' ? (typeNode.firstNamedChild ?? typeNode) : typeNode;
163
+ // Python subscript: List[User], Sequence[User] — use raw text
164
+ if (inner.type === 'subscript') {
165
+ return extractElementTypeFromString(inner.text, pos);
166
+ }
167
+ // generic_type: dict[str, User] — tree-sitter-python uses type_parameter child
168
+ if (inner.type === 'generic_type') {
169
+ // Try standard extractGenericTypeArgs first (handles type_arguments)
170
+ const args = extractGenericTypeArgs(inner);
171
+ if (args.length >= 1)
172
+ return pos === 'first' ? args[0] : args[args.length - 1];
173
+ // Fallback: look for type_parameter child (tree-sitter-python specific)
174
+ for (let i = 0; i < inner.namedChildCount; i++) {
175
+ const child = inner.namedChild(i);
176
+ if (child?.type === 'type_parameter') {
177
+ if (pos === 'first') {
178
+ const firstArg = child.firstNamedChild;
179
+ if (firstArg)
180
+ return extractSimpleTypeName(firstArg);
181
+ }
182
+ else {
183
+ const lastArg = child.lastNamedChild;
184
+ if (lastArg)
185
+ return extractSimpleTypeName(lastArg);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ // Fallback: raw text extraction (handles User[], [User], etc.)
191
+ return extractElementTypeFromString(inner.text, pos);
192
+ };
193
+ /**
194
+ * Walk up the AST from a for-statement to find the enclosing function definition,
195
+ * then search its parameters for one named `iterableName`.
196
+ * Returns the element type extracted from its type annotation, or undefined.
197
+ *
198
+ * Handles both `parameter` and `typed_parameter` node types in tree-sitter-python.
199
+ * `typed_parameter` may not expose the name as a `name` field — falls back to
200
+ * checking the first identifier-type named child.
201
+ */
202
+ const findPyParamElementType = (iterableName, startNode, pos = 'last') => {
203
+ let current = startNode.parent;
204
+ while (current) {
205
+ if (current.type === 'function_definition') {
206
+ const paramsNode = current.childForFieldName('parameters');
207
+ if (paramsNode) {
208
+ for (let i = 0; i < paramsNode.namedChildCount; i++) {
209
+ const param = paramsNode.namedChild(i);
210
+ if (!param)
211
+ continue;
212
+ // Try named `name` field first (parameter node), then first identifier child
213
+ // (typed_parameter node may store name as first positional child)
214
+ const nameNode = param.childForFieldName('name')
215
+ ?? (param.firstNamedChild?.type === 'identifier' ? param.firstNamedChild : null);
216
+ if (nameNode?.text !== iterableName)
217
+ continue;
218
+ // Try `type` field, then last named child (typed_parameter stores type last)
219
+ const typeAnnotation = param.childForFieldName('type')
220
+ ?? (param.namedChildCount >= 2 ? param.namedChild(param.namedChildCount - 1) : null);
221
+ if (typeAnnotation && typeAnnotation !== nameNode) {
222
+ return extractPyElementTypeFromAnnotation(typeAnnotation, pos);
223
+ }
224
+ }
225
+ }
226
+ break;
227
+ }
228
+ current = current.parent;
229
+ }
230
+ return undefined;
231
+ };
232
+ /**
233
+ * Python: for user in users: where users has a known container type annotation.
234
+ *
235
+ * AST node: `for_statement` with `left` (loop variable) and `right` (iterable).
236
+ *
237
+ * Tier 1c: resolves the element type via three strategies in priority order:
238
+ * 1. declarationTypeNodes — raw type annotation AST node (covers stored container types)
239
+ * 2. scopeEnv string — extractElementTypeFromString on the stored type
240
+ * 3. AST walk — walks up to the enclosing function's parameters to read List[User] directly
241
+ */
242
+ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }) => {
243
+ if (node.type !== 'for_statement')
244
+ return;
245
+ // The iterable is the `right` field — may be identifier, attribute, or call.
246
+ const rightNode = node.childForFieldName('right');
247
+ let iterableName;
248
+ let methodName;
249
+ let callExprElementType;
250
+ if (rightNode?.type === 'identifier') {
251
+ iterableName = rightNode.text;
252
+ }
253
+ else if (rightNode?.type === 'attribute') {
254
+ const prop = rightNode.lastNamedChild;
255
+ if (prop)
256
+ iterableName = prop.text;
257
+ }
258
+ else if (rightNode?.type === 'call') {
259
+ // data.items() → call > function: attribute > identifier('data') + identifier('items')
260
+ // get_users() → call > function: identifier (Phase 7.3 — return-type path)
261
+ const fn = rightNode.childForFieldName('function');
262
+ if (fn?.type === 'attribute') {
263
+ const obj = fn.firstNamedChild;
264
+ if (obj?.type === 'identifier')
265
+ iterableName = obj.text;
266
+ // Extract method name: items, keys, values
267
+ const method = fn.lastNamedChild;
268
+ if (method?.type === 'identifier' && method !== obj)
269
+ methodName = method.text;
270
+ }
271
+ else if (fn?.type === 'identifier') {
272
+ // Direct function call: for user in get_users()
273
+ const rawReturn = returnTypeLookup.lookupRawReturnType(fn.text);
274
+ if (rawReturn)
275
+ callExprElementType = extractElementTypeFromString(rawReturn);
276
+ }
277
+ }
278
+ if (!iterableName && !callExprElementType)
279
+ return;
280
+ let elementType;
281
+ if (callExprElementType) {
282
+ elementType = callExprElementType;
283
+ }
284
+ else {
285
+ const containerTypeName = scopeEnv.get(iterableName);
286
+ const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
287
+ elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractPyElementTypeFromAnnotation, findPyParamElementType, typeArgPos);
288
+ }
289
+ if (!elementType)
290
+ return;
291
+ // The loop variable is the `left` field — identifier or pattern_list.
292
+ const leftNode = node.childForFieldName('left');
293
+ if (!leftNode)
294
+ return;
295
+ // Handle tuple unpacking: for key, value in data.items()
296
+ if (leftNode.type === 'pattern_list') {
297
+ const lastChild = leftNode.lastNamedChild;
298
+ if (lastChild?.type === 'identifier') {
299
+ scopeEnv.set(lastChild.text, elementType);
300
+ }
301
+ return;
302
+ }
303
+ const loopVarName = extractVarName(leftNode);
304
+ if (loopVarName)
305
+ scopeEnv.set(loopVarName, elementType);
306
+ };
307
+ /** Python: alias = u → assignment with left/right fields.
308
+ * Also handles walrus operator: alias := u → named_expression with name/value fields. */
309
+ const extractPendingAssignment = (node, scopeEnv) => {
310
+ let left;
311
+ let right;
312
+ if (node.type === 'assignment') {
313
+ left = node.childForFieldName('left');
314
+ right = node.childForFieldName('right');
315
+ }
316
+ else if (node.type === 'named_expression') {
317
+ left = node.childForFieldName('name');
318
+ right = node.childForFieldName('value');
319
+ }
320
+ else {
321
+ return undefined;
322
+ }
323
+ if (!left || !right)
324
+ return undefined;
325
+ const lhs = left.type === 'identifier' ? left.text : undefined;
326
+ if (!lhs || scopeEnv.has(lhs))
327
+ return undefined;
328
+ if (right.type === 'identifier')
329
+ return { kind: 'copy', lhs, rhs: right.text };
330
+ return undefined;
331
+ };
332
+ /**
333
+ * Python match/case `as` pattern binding: `case User() as u:`
334
+ *
335
+ * AST structure (tree-sitter-python):
336
+ * as_pattern
337
+ * alias: as_pattern_target ← the bound variable name (e.g. "u")
338
+ * children[0]: case_pattern ← wraps class_pattern (or is class_pattern directly)
339
+ * class_pattern
340
+ * dotted_name ← the class name (e.g. "User")
341
+ *
342
+ * The `alias` field is an `as_pattern_target` node whose `.text` is the identifier.
343
+ * The class name lives in the first non-alias named child: either a `case_pattern`
344
+ * wrapping a `class_pattern`, or a direct `class_pattern`.
345
+ *
346
+ * Conservative: returns undefined when:
347
+ * - The node is not an `as_pattern`
348
+ * - The pattern side is not a class_pattern (e.g. guard or literal match)
349
+ * - The variable was already bound in scopeEnv
350
+ */
351
+ const extractPatternBinding = (node, scopeEnv) => {
352
+ if (node.type !== 'as_pattern')
353
+ return undefined;
354
+ // as_pattern: `case User() as u:` — binds matched value to a name.
355
+ // Try named field first (future grammar versions may expose it), fall back to positional.
356
+ if (node.namedChildCount < 2)
357
+ return undefined;
358
+ const patternChild = node.namedChild(0);
359
+ const varNameNode = node.childForFieldName('alias')
360
+ ?? node.namedChild(node.namedChildCount - 1);
361
+ if (!patternChild || !varNameNode)
362
+ return undefined;
363
+ if (varNameNode.type !== 'identifier')
364
+ return undefined;
365
+ const varName = varNameNode.text;
366
+ if (!varName || scopeEnv.has(varName))
367
+ return undefined;
368
+ // Find the class_pattern — may be direct or wrapped in case_pattern.
369
+ let classPattern = null;
370
+ if (patternChild.type === 'class_pattern') {
371
+ classPattern = patternChild;
372
+ }
373
+ else if (patternChild.type === 'case_pattern') {
374
+ // Unwrap one level: case_pattern wraps class_pattern
375
+ for (let j = 0; j < patternChild.namedChildCount; j++) {
376
+ const inner = patternChild.namedChild(j);
377
+ if (inner?.type === 'class_pattern') {
378
+ classPattern = inner;
379
+ break;
380
+ }
381
+ }
382
+ }
383
+ if (!classPattern)
384
+ return undefined;
385
+ // class_pattern children: dotted_name (the class name) + optional keyword_pattern args.
386
+ const classNameNode = classPattern.firstNamedChild;
387
+ if (!classNameNode || (classNameNode.type !== 'dotted_name' && classNameNode.type !== 'identifier'))
388
+ return undefined;
389
+ const typeName = classNameNode.text;
390
+ if (!typeName)
391
+ return undefined;
392
+ return { varName, typeName };
393
+ };
394
+ const PATTERN_BINDING_NODE_TYPES = new Set(['as_pattern']);
395
+ export const typeConfig = {
396
+ declarationNodeTypes: DECLARATION_NODE_TYPES,
397
+ forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
398
+ patternBindingNodeTypes: PATTERN_BINDING_NODE_TYPES,
399
+ extractDeclaration,
400
+ extractParameter,
401
+ extractInitializer,
402
+ scanConstructorBinding,
403
+ extractForLoopBinding,
404
+ extractPendingAssignment,
405
+ extractPatternBinding,
406
+ };
@@ -0,0 +1,2 @@
1
+ import type { LanguageTypeConfig } from './types.js';
2
+ export declare const typeConfig: LanguageTypeConfig;