codemesh 0.1.1__py3-none-any.whl

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 (52) hide show
  1. codemesh/__init__.py +5 -0
  2. codemesh/__main__.py +8 -0
  3. codemesh/cli/__init__.py +3 -0
  4. codemesh/cli/init.py +208 -0
  5. codemesh/cli/install_cmd.py +208 -0
  6. codemesh/cli/main.py +469 -0
  7. codemesh/context/__init__.py +3 -0
  8. codemesh/context/builder.py +388 -0
  9. codemesh/db/__init__.py +3 -0
  10. codemesh/db/connection.py +66 -0
  11. codemesh/db/queries.py +696 -0
  12. codemesh/db/schema.py +125 -0
  13. codemesh/embedding/__init__.py +3 -0
  14. codemesh/extraction/__init__.py +7 -0
  15. codemesh/extraction/languages/__init__.py +95 -0
  16. codemesh/extraction/languages/c_family.py +614 -0
  17. codemesh/extraction/languages/go.py +397 -0
  18. codemesh/extraction/languages/java.py +603 -0
  19. codemesh/extraction/languages/python.py +718 -0
  20. codemesh/extraction/languages/rust.py +435 -0
  21. codemesh/extraction/languages/swift.py +464 -0
  22. codemesh/extraction/languages/typescript.py +1222 -0
  23. codemesh/extraction/orchestrator.py +218 -0
  24. codemesh/graph/__init__.py +8 -0
  25. codemesh/graph/query_manager.py +117 -0
  26. codemesh/graph/traverser.py +107 -0
  27. codemesh/indexer.py +240 -0
  28. codemesh/mcp/__init__.py +3 -0
  29. codemesh/mcp/server.py +60 -0
  30. codemesh/mcp/tools.py +605 -0
  31. codemesh/querier.py +269 -0
  32. codemesh/resolution/__init__.py +7 -0
  33. codemesh/resolution/frameworks/__init__.py +15 -0
  34. codemesh/resolution/frameworks/django.py +30 -0
  35. codemesh/resolution/frameworks/fastapi.py +23 -0
  36. codemesh/resolution/import_resolver.py +69 -0
  37. codemesh/resolution/name_matcher.py +30 -0
  38. codemesh/resolution/resolver.py +268 -0
  39. codemesh/retrieval/__init__.py +7 -0
  40. codemesh/search/__init__.py +3 -0
  41. codemesh/sync/__init__.py +3 -0
  42. codemesh/sync/watcher.py +135 -0
  43. codemesh/types.py +148 -0
  44. codemesh/viz/__init__.py +0 -0
  45. codemesh/viz/graph_builder.py +162 -0
  46. codemesh/viz/server.py +122 -0
  47. codemesh/viz/templates/index.html +359 -0
  48. codemesh-0.1.1.dist-info/METADATA +337 -0
  49. codemesh-0.1.1.dist-info/RECORD +52 -0
  50. codemesh-0.1.1.dist-info/WHEEL +4 -0
  51. codemesh-0.1.1.dist-info/entry_points.txt +2 -0
  52. codemesh-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,397 @@
1
+ """Go tree-sitter extractor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import logging
7
+ from pathlib import Path # noqa: TC003
8
+ from typing import Any
9
+
10
+ from codemesh.types import Edge, EdgeKind, Language, Node, NodeKind
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class GoExtractor:
16
+ """Extracts Go code symbols from tree-sitter AST."""
17
+
18
+ def extract(
19
+ self,
20
+ file_path: Path,
21
+ source: bytes,
22
+ root_node: Any,
23
+ language: Language,
24
+ ) -> tuple[list[Node], list[Edge]]:
25
+ nodes: list[Node] = []
26
+ edges: list[Edge] = []
27
+
28
+ file_id = self._node_id(file_path, 1, root_node.end_point[0] + 1)
29
+ file_node = Node(
30
+ id=file_id,
31
+ kind=NodeKind.FILE,
32
+ name=file_path.name,
33
+ qualified_name=str(file_path),
34
+ file_path=file_path,
35
+ language=language,
36
+ start_line=1,
37
+ end_line=root_node.end_point[0] + 1,
38
+ )
39
+ nodes.append(file_node)
40
+
41
+ self._walk(source, root_node, file_path, file_id, nodes, edges)
42
+ return nodes, edges
43
+
44
+ def _walk(
45
+ self,
46
+ source: bytes,
47
+ node: Any,
48
+ file_path: Path,
49
+ parent_id: str,
50
+ nodes: list[Node],
51
+ edges: list[Edge],
52
+ ) -> None:
53
+ kind = node.type
54
+
55
+ if kind == "function_declaration":
56
+ self._extract_function(source, node, file_path, parent_id, nodes, edges)
57
+ elif kind == "method_declaration":
58
+ self._extract_method(source, node, file_path, parent_id, nodes, edges)
59
+ elif kind == "type_declaration":
60
+ self._extract_type_declaration(source, node, file_path, parent_id, nodes, edges)
61
+ elif kind == "const_declaration":
62
+ self._extract_constant(source, node, file_path, parent_id, nodes, edges)
63
+ elif kind == "import_declaration":
64
+ self._extract_import(source, node, file_path, parent_id, nodes, edges)
65
+ else:
66
+ for child in node.children:
67
+ self._walk(source, child, file_path, parent_id, nodes, edges)
68
+
69
+ def _extract_function(
70
+ self,
71
+ source: bytes,
72
+ node: Any,
73
+ file_path: Path,
74
+ parent_id: str,
75
+ nodes: list[Node],
76
+ edges: list[Edge],
77
+ ) -> str:
78
+ name_node = node.child_by_field_name("name")
79
+ if name_node is None:
80
+ return ""
81
+ name = source[name_node.start_byte : name_node.end_byte].decode()
82
+ start_line = node.start_point[0] + 1
83
+ end_line = node.end_point[0] + 1
84
+ node_id = self._node_id(file_path, start_line, end_line)
85
+
86
+ qualified = self._build_qualified_name(file_path, name, parent_id, nodes)
87
+ func_node = Node(
88
+ id=node_id,
89
+ kind=NodeKind.FUNCTION,
90
+ name=name,
91
+ qualified_name=qualified,
92
+ file_path=file_path,
93
+ language=Language.GO,
94
+ start_line=start_line,
95
+ end_line=end_line,
96
+ parent_id=parent_id,
97
+ )
98
+ nodes.append(func_node)
99
+ edges.append(
100
+ Edge(
101
+ id=self._edge_id(parent_id, node_id, EdgeKind.CONTAINS),
102
+ source_id=parent_id,
103
+ target_id=node_id,
104
+ kind=EdgeKind.CONTAINS,
105
+ )
106
+ )
107
+ self._extract_calls(source, node, node_id, file_path, edges)
108
+ return node_id
109
+
110
+ def _extract_method(
111
+ self,
112
+ source: bytes,
113
+ node: Any,
114
+ file_path: Path,
115
+ parent_id: str,
116
+ nodes: list[Node],
117
+ edges: list[Edge],
118
+ ) -> str:
119
+ name_node = node.child_by_field_name("name")
120
+ if name_node is None:
121
+ return ""
122
+ name = source[name_node.start_byte : name_node.end_byte].decode()
123
+ start_line = node.start_point[0] + 1
124
+ end_line = node.end_point[0] + 1
125
+ node_id = self._node_id(file_path, start_line, end_line)
126
+
127
+ qualified = self._build_qualified_name(file_path, name, parent_id, nodes)
128
+ method_node = Node(
129
+ id=node_id,
130
+ kind=NodeKind.METHOD,
131
+ name=name,
132
+ qualified_name=qualified,
133
+ file_path=file_path,
134
+ language=Language.GO,
135
+ start_line=start_line,
136
+ end_line=end_line,
137
+ parent_id=parent_id,
138
+ )
139
+ nodes.append(method_node)
140
+ edges.append(
141
+ Edge(
142
+ id=self._edge_id(parent_id, node_id, EdgeKind.CONTAINS),
143
+ source_id=parent_id,
144
+ target_id=node_id,
145
+ kind=EdgeKind.CONTAINS,
146
+ )
147
+ )
148
+ self._extract_calls(source, node, node_id, file_path, edges)
149
+ return node_id
150
+
151
+ def _extract_type_declaration(
152
+ self,
153
+ source: bytes,
154
+ node: Any,
155
+ file_path: Path,
156
+ parent_id: str,
157
+ nodes: list[Node],
158
+ edges: list[Edge],
159
+ ) -> str:
160
+ # type_declaration contains type_spec children
161
+ for child in node.children:
162
+ if child.type == "type_spec":
163
+ type_spec_child = child.child_by_field_name("type")
164
+ if type_spec_child is None:
165
+ continue
166
+ if type_spec_child.type == "struct_type":
167
+ self._extract_struct(source, child, file_path, parent_id, nodes, edges)
168
+ elif type_spec_child.type == "interface_type":
169
+ self._extract_interface(source, child, file_path, parent_id, nodes, edges)
170
+ return ""
171
+
172
+ def _extract_struct(
173
+ self,
174
+ source: bytes,
175
+ node: Any,
176
+ file_path: Path,
177
+ parent_id: str,
178
+ nodes: list[Node],
179
+ edges: list[Edge],
180
+ ) -> str:
181
+ name_node = node.child_by_field_name("name")
182
+ if name_node is None:
183
+ return ""
184
+ name = source[name_node.start_byte : name_node.end_byte].decode()
185
+ start_line = node.start_point[0] + 1
186
+ end_line = node.end_point[0] + 1
187
+ node_id = self._node_id(file_path, start_line, end_line)
188
+ qualified = self._build_qualified_name(file_path, name, parent_id, nodes)
189
+ struct_node = Node(
190
+ id=node_id,
191
+ kind=NodeKind.STRUCT,
192
+ name=name,
193
+ qualified_name=qualified,
194
+ file_path=file_path,
195
+ language=Language.GO,
196
+ start_line=start_line,
197
+ end_line=end_line,
198
+ parent_id=parent_id,
199
+ )
200
+ nodes.append(struct_node)
201
+ edges.append(
202
+ Edge(
203
+ id=self._edge_id(parent_id, node_id, EdgeKind.CONTAINS),
204
+ source_id=parent_id,
205
+ target_id=node_id,
206
+ kind=EdgeKind.CONTAINS,
207
+ )
208
+ )
209
+ return node_id
210
+
211
+ def _extract_interface(
212
+ self,
213
+ source: bytes,
214
+ node: Any,
215
+ file_path: Path,
216
+ parent_id: str,
217
+ nodes: list[Node],
218
+ edges: list[Edge],
219
+ ) -> str:
220
+ name_node = node.child_by_field_name("name")
221
+ if name_node is None:
222
+ return ""
223
+ name = source[name_node.start_byte : name_node.end_byte].decode()
224
+ start_line = node.start_point[0] + 1
225
+ end_line = node.end_point[0] + 1
226
+ node_id = self._node_id(file_path, start_line, end_line)
227
+ qualified = self._build_qualified_name(file_path, name, parent_id, nodes)
228
+ iface_node = Node(
229
+ id=node_id,
230
+ kind=NodeKind.INTERFACE,
231
+ name=name,
232
+ qualified_name=qualified,
233
+ file_path=file_path,
234
+ language=Language.GO,
235
+ start_line=start_line,
236
+ end_line=end_line,
237
+ parent_id=parent_id,
238
+ )
239
+ nodes.append(iface_node)
240
+ edges.append(
241
+ Edge(
242
+ id=self._edge_id(parent_id, node_id, EdgeKind.CONTAINS),
243
+ source_id=parent_id,
244
+ target_id=node_id,
245
+ kind=EdgeKind.CONTAINS,
246
+ )
247
+ )
248
+ return node_id
249
+
250
+ def _extract_constant(
251
+ self,
252
+ source: bytes,
253
+ node: Any,
254
+ file_path: Path,
255
+ parent_id: str,
256
+ nodes: list[Node],
257
+ edges: list[Edge],
258
+ ) -> str:
259
+ # const_declaration may contain multiple const_spec children
260
+ for child in node.children:
261
+ if child.type == "const_spec":
262
+ name_node = child.child_by_field_name("name")
263
+ if name_node is None:
264
+ continue
265
+ name = source[name_node.start_byte : name_node.end_byte].decode()
266
+ start_line = child.start_point[0] + 1
267
+ end_line = child.end_point[0] + 1
268
+ node_id = self._node_id(file_path, start_line, end_line)
269
+ qualified = self._build_qualified_name(file_path, name, parent_id, nodes)
270
+ const_node = Node(
271
+ id=node_id,
272
+ kind=NodeKind.CONSTANT,
273
+ name=name,
274
+ qualified_name=qualified,
275
+ file_path=file_path,
276
+ language=Language.GO,
277
+ start_line=start_line,
278
+ end_line=end_line,
279
+ parent_id=parent_id,
280
+ )
281
+ nodes.append(const_node)
282
+ edges.append(
283
+ Edge(
284
+ id=self._edge_id(parent_id, node_id, EdgeKind.CONTAINS),
285
+ source_id=parent_id,
286
+ target_id=node_id,
287
+ kind=EdgeKind.CONTAINS,
288
+ )
289
+ )
290
+ return ""
291
+
292
+ def _extract_import(
293
+ self,
294
+ source: bytes,
295
+ node: Any,
296
+ file_path: Path,
297
+ parent_id: str,
298
+ nodes: list[Node],
299
+ edges: list[Edge],
300
+ ) -> None:
301
+ start_line = node.start_point[0] + 1
302
+ end_line = node.end_point[0] + 1
303
+ node_id = self._node_id(file_path, start_line, end_line)
304
+ text = source[node.start_byte : node.end_byte].decode().strip()
305
+ import_node = Node(
306
+ id=node_id,
307
+ kind=NodeKind.IMPORT,
308
+ name=text[:80],
309
+ qualified_name=f"import:{text[:80]}",
310
+ file_path=file_path,
311
+ language=Language.GO,
312
+ start_line=start_line,
313
+ end_line=end_line,
314
+ parent_id=parent_id,
315
+ )
316
+ nodes.append(import_node)
317
+ edges.append(
318
+ Edge(
319
+ id=self._edge_id(parent_id, node_id, EdgeKind.CONTAINS),
320
+ source_id=parent_id,
321
+ target_id=node_id,
322
+ kind=EdgeKind.CONTAINS,
323
+ )
324
+ )
325
+ edges.append(
326
+ Edge(
327
+ id=self._edge_id(parent_id, f"unresolved:{text}", EdgeKind.IMPORTS),
328
+ source_id=parent_id,
329
+ target_id=f"unresolved:{text}",
330
+ kind=EdgeKind.IMPORTS,
331
+ confidence=0.5,
332
+ )
333
+ )
334
+
335
+ def _extract_calls(
336
+ self,
337
+ source: bytes,
338
+ func_node: Any,
339
+ func_id: str,
340
+ file_path: Path,
341
+ edges: list[Edge],
342
+ ) -> None:
343
+ body = func_node.child_by_field_name("body")
344
+ if body is None:
345
+ return
346
+ self._find_calls(source, body, func_id, file_path, edges)
347
+
348
+ def _find_calls(
349
+ self,
350
+ source: bytes,
351
+ node: Any,
352
+ caller_id: str,
353
+ file_path: Path,
354
+ edges: list[Edge],
355
+ ) -> None:
356
+ if node.type == "call_expression":
357
+ func = node.child_by_field_name("function")
358
+ if func:
359
+ call_name = source[func.start_byte : func.end_byte].decode()
360
+ edges.append(
361
+ Edge(
362
+ id=self._edge_id(caller_id, f"unresolved:{call_name}", EdgeKind.CALLS),
363
+ source_id=caller_id,
364
+ target_id=f"unresolved:{call_name}",
365
+ kind=EdgeKind.CALLS,
366
+ confidence=0.5,
367
+ line=node.start_point[0] + 1,
368
+ )
369
+ )
370
+ for child in node.children:
371
+ self._find_calls(source, child, caller_id, file_path, edges)
372
+
373
+ def _build_qualified_name(
374
+ self,
375
+ file_path: Path,
376
+ name: str,
377
+ parent_id: str,
378
+ nodes: list[Node],
379
+ ) -> str:
380
+ for n in nodes:
381
+ if n.id == parent_id:
382
+ if n.kind in (NodeKind.STRUCT, NodeKind.INTERFACE, NodeKind.ENUM, NodeKind.MODULE):
383
+ return f"{n.qualified_name}::{name}"
384
+ elif n.kind == NodeKind.FILE:
385
+ return f"{file_path.stem}::{name}"
386
+ break
387
+ return name
388
+
389
+ @staticmethod
390
+ def _node_id(file: Path, start: int, end: int) -> str:
391
+ raw = f"{file}:{start}:{end}"
392
+ return hashlib.sha256(raw.encode()).hexdigest()[:16]
393
+
394
+ @staticmethod
395
+ def _edge_id(source: str, target: str, kind: EdgeKind) -> str:
396
+ raw = f"{source}:{target}:{kind.value}"
397
+ return hashlib.sha256(raw.encode()).hexdigest()[:16]