superlocalmemory 3.3.20 → 3.3.21

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 (78) hide show
  1. package/package.json +1 -1
  2. package/pyproject.toml +9 -1
  3. package/src/superlocalmemory/cli/commands.py +138 -22
  4. package/src/superlocalmemory/cli/daemon.py +372 -0
  5. package/src/superlocalmemory/cli/main.py +8 -0
  6. package/src/superlocalmemory/cli/pending_store.py +158 -0
  7. package/src/superlocalmemory/cli/setup_wizard.py +39 -6
  8. package/src/superlocalmemory/code_graph/__init__.py +46 -0
  9. package/src/superlocalmemory/code_graph/blast_radius.py +177 -0
  10. package/src/superlocalmemory/code_graph/bridge/__init__.py +36 -0
  11. package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +464 -0
  12. package/src/superlocalmemory/code_graph/bridge/event_listeners.py +195 -0
  13. package/src/superlocalmemory/code_graph/bridge/fact_enricher.py +159 -0
  14. package/src/superlocalmemory/code_graph/bridge/hebbian_linker.py +170 -0
  15. package/src/superlocalmemory/code_graph/bridge/temporal_checker.py +152 -0
  16. package/src/superlocalmemory/code_graph/changes.py +363 -0
  17. package/src/superlocalmemory/code_graph/communities.py +299 -0
  18. package/src/superlocalmemory/code_graph/config.py +88 -0
  19. package/src/superlocalmemory/code_graph/database.py +482 -0
  20. package/src/superlocalmemory/code_graph/extractors/__init__.py +78 -0
  21. package/src/superlocalmemory/code_graph/extractors/python.py +413 -0
  22. package/src/superlocalmemory/code_graph/extractors/typescript.py +556 -0
  23. package/src/superlocalmemory/code_graph/flows.py +350 -0
  24. package/src/superlocalmemory/code_graph/git_hooks.py +226 -0
  25. package/src/superlocalmemory/code_graph/graph_engine.py +295 -0
  26. package/src/superlocalmemory/code_graph/graph_store.py +158 -0
  27. package/src/superlocalmemory/code_graph/incremental.py +200 -0
  28. package/src/superlocalmemory/code_graph/models.py +130 -0
  29. package/src/superlocalmemory/code_graph/parser.py +507 -0
  30. package/src/superlocalmemory/code_graph/resolver.py +321 -0
  31. package/src/superlocalmemory/code_graph/search.py +460 -0
  32. package/src/superlocalmemory/code_graph/service.py +95 -0
  33. package/src/superlocalmemory/code_graph/watcher.py +207 -0
  34. package/src/superlocalmemory/core/embedding_worker.py +4 -2
  35. package/src/superlocalmemory/core/embeddings.py +8 -2
  36. package/src/superlocalmemory/core/engine.py +32 -0
  37. package/src/superlocalmemory/core/engine_wiring.py +5 -0
  38. package/src/superlocalmemory/core/store_pipeline.py +23 -1
  39. package/src/superlocalmemory/encoding/fact_extractor.py +68 -7
  40. package/src/superlocalmemory/infra/event_bus.py +5 -0
  41. package/src/superlocalmemory/mcp/server.py +23 -0
  42. package/src/superlocalmemory/mcp/tools_code_graph.py +1592 -0
  43. package/src/superlocalmemory/retrieval/engine.py +137 -2
  44. package/src/superlocalmemory/retrieval/semantic_channel.py +6 -2
  45. package/src/superlocalmemory/retrieval/spreading_activation.py +5 -3
  46. package/src/superlocalmemory/retrieval/strategy.py +16 -0
  47. package/src/superlocalmemory/server/api.py +4 -2
  48. package/src/superlocalmemory/server/ui.py +5 -2
  49. package/src/superlocalmemory/storage/schema_code_graph.py +239 -0
  50. package/src/superlocalmemory/ui/index.html +1879 -0
  51. package/src/superlocalmemory/ui/js/agents.js +192 -0
  52. package/src/superlocalmemory/ui/js/auto-settings.js +399 -0
  53. package/src/superlocalmemory/ui/js/behavioral.js +276 -0
  54. package/src/superlocalmemory/ui/js/clusters.js +206 -0
  55. package/src/superlocalmemory/ui/js/compliance.js +252 -0
  56. package/src/superlocalmemory/ui/js/core.js +246 -0
  57. package/src/superlocalmemory/ui/js/dashboard.js +110 -0
  58. package/src/superlocalmemory/ui/js/events.js +178 -0
  59. package/src/superlocalmemory/ui/js/fact-detail.js +92 -0
  60. package/src/superlocalmemory/ui/js/feedback.js +333 -0
  61. package/src/superlocalmemory/ui/js/graph-core.js +447 -0
  62. package/src/superlocalmemory/ui/js/graph-filters.js +220 -0
  63. package/src/superlocalmemory/ui/js/graph-interactions.js +351 -0
  64. package/src/superlocalmemory/ui/js/graph-ui.js +214 -0
  65. package/src/superlocalmemory/ui/js/ide-status.js +102 -0
  66. package/src/superlocalmemory/ui/js/init.js +45 -0
  67. package/src/superlocalmemory/ui/js/learning.js +435 -0
  68. package/src/superlocalmemory/ui/js/lifecycle.js +298 -0
  69. package/src/superlocalmemory/ui/js/math-health.js +98 -0
  70. package/src/superlocalmemory/ui/js/memories.js +264 -0
  71. package/src/superlocalmemory/ui/js/modal.js +357 -0
  72. package/src/superlocalmemory/ui/js/patterns.js +93 -0
  73. package/src/superlocalmemory/ui/js/profiles.js +236 -0
  74. package/src/superlocalmemory/ui/js/recall-lab.js +292 -0
  75. package/src/superlocalmemory/ui/js/search.js +59 -0
  76. package/src/superlocalmemory/ui/js/settings.js +224 -0
  77. package/src/superlocalmemory/ui/js/timeline.js +32 -0
  78. package/src/superlocalmemory/ui/js/trust-dashboard.js +73 -0
@@ -0,0 +1,556 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory v3.4 — CodeGraph Module
4
+
5
+ """TypeScript/JavaScript AST extractor using tree-sitter.
6
+
7
+ Handles .ts, .tsx, .js, .jsx via appropriate grammar selection.
8
+ Extracts: function_declaration, arrow_function, class_declaration,
9
+ interface_declaration, import_statement, call_expression, JSX elements.
10
+
11
+ tree-sitter imports are lazy (HR-07).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ from typing import Any
19
+
20
+ from superlocalmemory.code_graph.config import CodeGraphConfig
21
+ from superlocalmemory.code_graph.extractors import BaseExtractor
22
+ from superlocalmemory.code_graph.models import (
23
+ EdgeKind,
24
+ GraphEdge,
25
+ GraphNode,
26
+ NodeKind,
27
+ )
28
+ from superlocalmemory.storage.models import _new_id
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # S-expression queries
34
+ # ---------------------------------------------------------------------------
35
+
36
+ FUNC_DECL_QUERY_SRC = (
37
+ "(function_declaration"
38
+ " name: (identifier) @func.name"
39
+ ") @func.def"
40
+ )
41
+
42
+ METHOD_DEF_QUERY_SRC = (
43
+ "(method_definition"
44
+ " name: (property_identifier) @method.name"
45
+ ") @method.def"
46
+ )
47
+
48
+ ARROW_QUERY_SRC = (
49
+ "(lexical_declaration"
50
+ " (variable_declarator"
51
+ " name: (identifier) @arrow.name"
52
+ " value: (arrow_function) @arrow.func"
53
+ " )"
54
+ ") @arrow.decl"
55
+ )
56
+
57
+ CLASS_QUERY_SRC = (
58
+ "(class_declaration"
59
+ " name: (type_identifier) @class.name"
60
+ ") @class.def"
61
+ )
62
+
63
+ IFACE_QUERY_SRC = (
64
+ "(interface_declaration"
65
+ " name: (type_identifier) @iface.name"
66
+ ") @iface.def"
67
+ )
68
+
69
+ IMPORT_QUERY_SRC = (
70
+ "(import_statement"
71
+ " source: (string) @import.source"
72
+ ") @import.stmt"
73
+ )
74
+
75
+ CALL_QUERY_SRC = (
76
+ "(call_expression"
77
+ " function: (identifier) @call.name"
78
+ ") @call.expr"
79
+ )
80
+
81
+ CALL_MEMBER_QUERY_SRC = (
82
+ "(call_expression"
83
+ " function: (member_expression"
84
+ " property: (property_identifier) @call.method)"
85
+ ") @call.member_expr"
86
+ )
87
+
88
+ JSX_ELEMENT_QUERY_SRC = (
89
+ "(jsx_opening_element"
90
+ " name: (identifier) @jsx.name"
91
+ ") @jsx.open"
92
+ )
93
+
94
+ JSX_SELF_CLOSING_QUERY_SRC = (
95
+ "(jsx_self_closing_element"
96
+ " name: (identifier) @jsx.name"
97
+ ") @jsx.self_close"
98
+ )
99
+
100
+ NEW_EXPR_QUERY_SRC = (
101
+ "(new_expression"
102
+ " constructor: (identifier) @new.name"
103
+ ") @new.expr"
104
+ )
105
+
106
+ EXPORT_QUERY_SRC = "(export_statement) @export.stmt"
107
+
108
+
109
+ def _get_ts_modules() -> tuple[Any, Any]:
110
+ """Lazy import of tree-sitter modules."""
111
+ try:
112
+ import tree_sitter
113
+ from tree_sitter_language_pack import get_language
114
+ except ImportError as exc:
115
+ raise ImportError(
116
+ "tree-sitter and tree-sitter-language-pack required for parsing. "
117
+ "Install: pip install 'superlocalmemory[code-graph]'"
118
+ ) from exc
119
+ return tree_sitter, get_language
120
+
121
+
122
+ def _node_text(node: Any, source: bytes) -> str:
123
+ """Extract text from a tree-sitter node."""
124
+ return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace")
125
+
126
+
127
+ def _find_parent_class(node: Any) -> str | None:
128
+ """Walk up AST to find enclosing class_declaration name."""
129
+ current = node.parent
130
+ while current is not None:
131
+ if current.type == "class_declaration":
132
+ name_node = current.child_by_field_name("name")
133
+ if name_node:
134
+ return name_node.text.decode("utf-8", errors="replace")
135
+ current = current.parent
136
+ return None
137
+
138
+
139
+ def _make_qualified_name(
140
+ file_path: str, name: str, parent_name: str | None
141
+ ) -> str:
142
+ if parent_name:
143
+ return f"{file_path}::{parent_name}.{name}"
144
+ return f"{file_path}::{name}"
145
+
146
+
147
+ def _run_query(
148
+ query_src: str, root_node: Any, language_name: str
149
+ ) -> list[tuple[int, dict[str, list[Any]]]]:
150
+ """Run a tree-sitter query and return matches."""
151
+ ts_mod, get_language = _get_ts_modules()
152
+ lang = get_language(language_name)
153
+ query = ts_mod.Query(lang, query_src)
154
+ cursor = ts_mod.QueryCursor(query)
155
+ return cursor.matches(root_node)
156
+
157
+
158
+ def _safe_query(
159
+ query_src: str, root_node: Any, language_name: str
160
+ ) -> list[tuple[int, dict[str, list[Any]]]]:
161
+ """Run query, returning empty list on error (some queries may not apply to all grammars)."""
162
+ try:
163
+ return _run_query(query_src, root_node, language_name)
164
+ except Exception:
165
+ return []
166
+
167
+
168
+ def _detect_language(file_path: str) -> str:
169
+ """Map file extension to tree-sitter grammar name."""
170
+ if file_path.endswith(".tsx"):
171
+ return "tsx"
172
+ if file_path.endswith(".ts"):
173
+ return "typescript"
174
+ if file_path.endswith(".jsx"):
175
+ return "javascript"
176
+ return "javascript"
177
+
178
+
179
+ class TypeScriptExtractor(BaseExtractor):
180
+ """TypeScript/JavaScript AST extractor."""
181
+
182
+ def __init__(
183
+ self,
184
+ root_node: Any,
185
+ source_bytes: bytes,
186
+ file_path: str,
187
+ config: CodeGraphConfig,
188
+ ) -> None:
189
+ super().__init__(root_node, source_bytes, file_path, config)
190
+ self._lang_name = _detect_language(file_path)
191
+ self._exports: list[str] = []
192
+
193
+ def extract_functions(self) -> list[GraphNode]:
194
+ """Extract function declarations, arrow functions, and methods."""
195
+ nodes: list[GraphNode] = []
196
+
197
+ # Function declarations
198
+ for _pat, captures in _run_query(FUNC_DECL_QUERY_SRC, self._root, self._lang_name):
199
+ func_defs = captures.get("func.def", [])
200
+ func_names = captures.get("func.name", [])
201
+ for i, func_def in enumerate(func_defs):
202
+ name_node = func_names[i] if i < len(func_names) else None
203
+ if not name_node:
204
+ continue
205
+ name = _node_text(name_node, self._source)
206
+ parent = _find_parent_class(func_def)
207
+ sig = self._build_func_signature(func_def, name)
208
+ nodes.append(GraphNode(
209
+ node_id=_new_id(),
210
+ kind=NodeKind.METHOD if parent else NodeKind.FUNCTION,
211
+ name=name,
212
+ qualified_name=_make_qualified_name(self._file_path, name, parent),
213
+ file_path=self._file_path,
214
+ line_start=func_def.start_point[0],
215
+ line_end=func_def.end_point[0],
216
+ language=self._lang_name,
217
+ parent_name=parent,
218
+ signature=sig,
219
+ ))
220
+
221
+ # Method definitions
222
+ for _pat, captures in _run_query(METHOD_DEF_QUERY_SRC, self._root, self._lang_name):
223
+ method_defs = captures.get("method.def", [])
224
+ method_names = captures.get("method.name", [])
225
+ for i, method_def in enumerate(method_defs):
226
+ name_node = method_names[i] if i < len(method_names) else None
227
+ if not name_node:
228
+ continue
229
+ name = _node_text(name_node, self._source)
230
+ parent = _find_parent_class(method_def)
231
+ sig = self._build_method_signature(method_def, name)
232
+ nodes.append(GraphNode(
233
+ node_id=_new_id(),
234
+ kind=NodeKind.METHOD,
235
+ name=name,
236
+ qualified_name=_make_qualified_name(self._file_path, name, parent),
237
+ file_path=self._file_path,
238
+ line_start=method_def.start_point[0],
239
+ line_end=method_def.end_point[0],
240
+ language=self._lang_name,
241
+ parent_name=parent,
242
+ signature=sig,
243
+ ))
244
+
245
+ # Arrow functions assigned to variables
246
+ for _pat, captures in _run_query(ARROW_QUERY_SRC, self._root, self._lang_name):
247
+ arrow_decls = captures.get("arrow.decl", [])
248
+ arrow_names = captures.get("arrow.name", [])
249
+ arrow_funcs = captures.get("arrow.func", [])
250
+ for i, decl in enumerate(arrow_decls):
251
+ name_node = arrow_names[i] if i < len(arrow_names) else None
252
+ func_node = arrow_funcs[i] if i < len(arrow_funcs) else None
253
+ if not name_node:
254
+ continue
255
+ name = _node_text(name_node, self._source)
256
+ parent = _find_parent_class(decl)
257
+ sig = self._build_arrow_signature(func_node, name) if func_node else f"const {name} = (...) => ..."
258
+ nodes.append(GraphNode(
259
+ node_id=_new_id(),
260
+ kind=NodeKind.METHOD if parent else NodeKind.FUNCTION,
261
+ name=name,
262
+ qualified_name=_make_qualified_name(self._file_path, name, parent),
263
+ file_path=self._file_path,
264
+ line_start=decl.start_point[0],
265
+ line_end=decl.end_point[0],
266
+ language=self._lang_name,
267
+ parent_name=parent,
268
+ signature=sig,
269
+ ))
270
+
271
+ return nodes
272
+
273
+ def extract_classes(self) -> list[GraphNode]:
274
+ """Extract class and interface declarations."""
275
+ nodes: list[GraphNode] = []
276
+ self._inherits_info: list[tuple[str, str, int]] = []
277
+
278
+ # Classes
279
+ for _pat, captures in _run_query(CLASS_QUERY_SRC, self._root, self._lang_name):
280
+ class_defs = captures.get("class.def", [])
281
+ class_names = captures.get("class.name", [])
282
+ for i, class_def in enumerate(class_defs):
283
+ name_node = class_names[i] if i < len(class_names) else None
284
+ if not name_node:
285
+ continue
286
+ name = _node_text(name_node, self._source)
287
+ qname = _make_qualified_name(self._file_path, name, None)
288
+ # Check for heritage clause (extends/implements)
289
+ self._extract_heritage(class_def, qname)
290
+ nodes.append(GraphNode(
291
+ node_id=_new_id(),
292
+ kind=NodeKind.CLASS,
293
+ name=name,
294
+ qualified_name=qname,
295
+ file_path=self._file_path,
296
+ line_start=class_def.start_point[0],
297
+ line_end=class_def.end_point[0],
298
+ language=self._lang_name,
299
+ ))
300
+
301
+ # Interfaces
302
+ for _pat, captures in _safe_query(IFACE_QUERY_SRC, self._root, self._lang_name):
303
+ iface_defs = captures.get("iface.def", [])
304
+ iface_names = captures.get("iface.name", [])
305
+ for i, iface_def in enumerate(iface_defs):
306
+ name_node = iface_names[i] if i < len(iface_names) else None
307
+ if not name_node:
308
+ continue
309
+ name = _node_text(name_node, self._source)
310
+ nodes.append(GraphNode(
311
+ node_id=_new_id(),
312
+ kind=NodeKind.CLASS,
313
+ name=name,
314
+ qualified_name=_make_qualified_name(self._file_path, name, None),
315
+ file_path=self._file_path,
316
+ line_start=iface_def.start_point[0],
317
+ line_end=iface_def.end_point[0],
318
+ language=self._lang_name,
319
+ ))
320
+
321
+ return nodes
322
+
323
+ def extract_imports(
324
+ self,
325
+ ) -> tuple[list[GraphEdge], dict[str, tuple[str, str]]]:
326
+ """Extract import statements and build import_map."""
327
+ edges: list[GraphEdge] = []
328
+ import_map: dict[str, tuple[str, str]] = {}
329
+
330
+ for _pat, captures in _run_query(IMPORT_QUERY_SRC, self._root, self._lang_name):
331
+ stmts = captures.get("import.stmt", [])
332
+ sources = captures.get("import.source", [])
333
+ for i, stmt in enumerate(stmts):
334
+ source_node = sources[i] if i < len(sources) else None
335
+ if not source_node:
336
+ continue
337
+ module_path = _node_text(source_node, self._source).strip("'\"")
338
+
339
+ # Parse import clause
340
+ for child in stmt.children:
341
+ if child.type == "import_clause":
342
+ self._parse_import_clause(child, module_path, import_map)
343
+
344
+ edges.append(GraphEdge(
345
+ edge_id=_new_id(),
346
+ kind=EdgeKind.IMPORTS,
347
+ source_node_id="__unresolved__",
348
+ target_node_id="__unresolved__",
349
+ file_path=self._file_path,
350
+ line=stmt.start_point[0],
351
+ extra_json=json.dumps({"module": module_path}),
352
+ ))
353
+
354
+ return edges, import_map
355
+
356
+ def extract_calls(
357
+ self, import_map: dict[str, tuple[str, str]]
358
+ ) -> list[GraphEdge]:
359
+ """Extract function/method calls and new expressions."""
360
+ edges: list[GraphEdge] = []
361
+
362
+ # Simple calls: foo()
363
+ for _pat, captures in _run_query(CALL_QUERY_SRC, self._root, self._lang_name):
364
+ call_names = captures.get("call.name", [])
365
+ for name_node in call_names:
366
+ call_name = _node_text(name_node, self._source)
367
+ confidence = 1.0 if call_name in import_map else self._config.heuristic_confidence
368
+ edges.append(GraphEdge(
369
+ edge_id=_new_id(),
370
+ kind=EdgeKind.CALLS,
371
+ source_node_id="__unresolved__",
372
+ target_node_id=f"__call__{call_name}",
373
+ file_path=self._file_path,
374
+ line=name_node.start_point[0],
375
+ confidence=confidence,
376
+ extra_json=json.dumps({"call_name": call_name}),
377
+ ))
378
+
379
+ # Member calls: obj.method()
380
+ for _pat, captures in _run_query(CALL_MEMBER_QUERY_SRC, self._root, self._lang_name):
381
+ method_names = captures.get("call.method", [])
382
+ for name_node in method_names:
383
+ method_name = _node_text(name_node, self._source)
384
+ edges.append(GraphEdge(
385
+ edge_id=_new_id(),
386
+ kind=EdgeKind.CALLS,
387
+ source_node_id="__unresolved__",
388
+ target_node_id=f"__call__{method_name}",
389
+ file_path=self._file_path,
390
+ line=name_node.start_point[0],
391
+ confidence=self._config.heuristic_confidence,
392
+ extra_json=json.dumps({"call_name": method_name, "is_method": True}),
393
+ ))
394
+
395
+ # new Foo()
396
+ for _pat, captures in _run_query(NEW_EXPR_QUERY_SRC, self._root, self._lang_name):
397
+ new_names = captures.get("new.name", [])
398
+ for name_node in new_names:
399
+ call_name = _node_text(name_node, self._source)
400
+ confidence = 1.0 if call_name in import_map else self._config.heuristic_confidence
401
+ edges.append(GraphEdge(
402
+ edge_id=_new_id(),
403
+ kind=EdgeKind.CALLS,
404
+ source_node_id="__unresolved__",
405
+ target_node_id=f"__call__{call_name}",
406
+ file_path=self._file_path,
407
+ line=name_node.start_point[0],
408
+ confidence=confidence,
409
+ extra_json=json.dumps({"call_name": call_name, "is_constructor": True}),
410
+ ))
411
+
412
+ # JSX components (tsx/jsx only)
413
+ if self._lang_name in ("tsx", "javascript"):
414
+ self._extract_jsx_calls(edges, import_map)
415
+
416
+ return edges
417
+
418
+ def extract_exports(self) -> list[str]:
419
+ """Extract exported names."""
420
+ exports: list[str] = []
421
+ for _pat, captures in _safe_query(EXPORT_QUERY_SRC, self._root, self._lang_name):
422
+ for stmt in captures.get("export.stmt", []):
423
+ # Look for named children that contain identifiers
424
+ for child in stmt.named_children:
425
+ if child.type == "function_declaration":
426
+ name_n = child.child_by_field_name("name")
427
+ if name_n:
428
+ exports.append(_node_text(name_n, self._source))
429
+ elif child.type == "class_declaration":
430
+ name_n = child.child_by_field_name("name")
431
+ if name_n:
432
+ exports.append(_node_text(name_n, self._source))
433
+ elif child.type == "lexical_declaration":
434
+ for decl in child.named_children:
435
+ if decl.type == "variable_declarator":
436
+ name_n = decl.child_by_field_name("name")
437
+ if name_n:
438
+ exports.append(_node_text(name_n, self._source))
439
+ self._exports = exports
440
+ return exports
441
+
442
+ def extract(self) -> tuple[list[GraphNode], list[GraphEdge]]:
443
+ """Override to also extract exports."""
444
+ self.extract_exports()
445
+ return super().extract()
446
+
447
+ # ------------------------------------------------------------------
448
+ # Private helpers
449
+ # ------------------------------------------------------------------
450
+
451
+ def _parse_import_clause(
452
+ self, clause: Any, module_path: str, import_map: dict[str, tuple[str, str]]
453
+ ) -> None:
454
+ """Parse an import_clause node into import_map entries."""
455
+ for child in clause.children:
456
+ if child.type == "identifier":
457
+ # Default import: import Foo from '...'
458
+ name = _node_text(child, self._source)
459
+ import_map[name] = (module_path, "default")
460
+ elif child.type == "named_imports":
461
+ for spec in child.named_children:
462
+ if spec.type == "import_specifier":
463
+ name_n = spec.child_by_field_name("name")
464
+ alias_n = spec.child_by_field_name("alias")
465
+ if name_n:
466
+ orig = _node_text(name_n, self._source)
467
+ alias = _node_text(alias_n, self._source) if alias_n else orig
468
+ import_map[alias] = (module_path, orig)
469
+ elif child.type == "namespace_import":
470
+ # import * as ns from '...'
471
+ for sub in child.children:
472
+ if sub.type == "identifier":
473
+ ns_name = _node_text(sub, self._source)
474
+ import_map[ns_name] = (module_path, "*")
475
+ break
476
+
477
+ def _extract_heritage(
478
+ self, class_node: Any, class_qname: str
479
+ ) -> None:
480
+ """Extract extends/implements from class heritage clause."""
481
+ for child in class_node.children:
482
+ if child.type == "class_heritage":
483
+ for heritage in child.named_children:
484
+ if heritage.type in ("extends_clause", "implements_clause"):
485
+ for type_node in heritage.named_children:
486
+ base_name = _node_text(type_node, self._source)
487
+ self._inherits_info.append(
488
+ (class_qname, base_name, class_node.start_point[0])
489
+ )
490
+
491
+ def _extract_jsx_calls(
492
+ self, edges: list[GraphEdge], import_map: dict[str, tuple[str, str]]
493
+ ) -> None:
494
+ """Extract JSX component usage as CALLS edges."""
495
+ for query_src in (JSX_ELEMENT_QUERY_SRC, JSX_SELF_CLOSING_QUERY_SRC):
496
+ for _pat, captures in _safe_query(query_src, self._root, self._lang_name):
497
+ for name_node in captures.get("jsx.name", []):
498
+ comp_name = _node_text(name_node, self._source)
499
+ # Only uppercase = React component (lowercase = HTML tag)
500
+ if comp_name and comp_name[0].isupper():
501
+ confidence = 1.0 if comp_name in import_map else self._config.heuristic_confidence
502
+ edges.append(GraphEdge(
503
+ edge_id=_new_id(),
504
+ kind=EdgeKind.CALLS,
505
+ source_node_id="__unresolved__",
506
+ target_node_id=f"__call__{comp_name}",
507
+ file_path=self._file_path,
508
+ line=name_node.start_point[0],
509
+ confidence=confidence,
510
+ extra_json=json.dumps({"call_name": comp_name, "is_jsx": True}),
511
+ ))
512
+
513
+ def _build_func_signature(self, func_def: Any, name: str) -> str:
514
+ """Build signature for function_declaration."""
515
+ params_node = func_def.child_by_field_name("parameters")
516
+ params = _node_text(params_node, self._source) if params_node else "()"
517
+ rt = func_def.child_by_field_name("return_type")
518
+ rt_text = _node_text(rt, self._source) if rt else ""
519
+ # Check for async keyword
520
+ is_async = any(
521
+ c.type == "async" for c in func_def.children
522
+ if hasattr(c, "type")
523
+ )
524
+ prefix = "async function" if is_async else "function"
525
+ sig = f"{prefix} {name}{params}"
526
+ if rt_text:
527
+ sig += rt_text
528
+ return sig
529
+
530
+ def _build_method_signature(self, method_def: Any, name: str) -> str:
531
+ """Build signature for method_definition."""
532
+ params_node = method_def.child_by_field_name("parameters")
533
+ params = _node_text(params_node, self._source) if params_node else "()"
534
+ rt = method_def.child_by_field_name("return_type")
535
+ rt_text = _node_text(rt, self._source) if rt else ""
536
+ is_async = any(
537
+ c.type == "async" for c in method_def.children
538
+ if hasattr(c, "type")
539
+ )
540
+ prefix = "async " if is_async else ""
541
+ sig = f"{prefix}{name}{params}"
542
+ if rt_text:
543
+ sig += rt_text
544
+ return sig
545
+
546
+ def _build_arrow_signature(self, arrow_func: Any, name: str) -> str:
547
+ """Build signature for arrow function."""
548
+ params_node = arrow_func.child_by_field_name("parameters")
549
+ params = _node_text(params_node, self._source) if params_node else "()"
550
+ rt = arrow_func.child_by_field_name("return_type")
551
+ rt_text = _node_text(rt, self._source) if rt else ""
552
+ sig = f"const {name} = {params}"
553
+ if rt_text:
554
+ sig += rt_text
555
+ sig += " => ..."
556
+ return sig