codegraph-nav 0.1.0__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 (41) hide show
  1. codegraph_nav/__init__.py +194 -0
  2. codegraph_nav/ast_grep_analyzer.py +448 -0
  3. codegraph_nav/cli.py +223 -0
  4. codegraph_nav/code_navigator.py +1328 -0
  5. codegraph_nav/code_search.py +1009 -0
  6. codegraph_nav/colors.py +209 -0
  7. codegraph_nav/completions.py +354 -0
  8. codegraph_nav/dart_analyzer.py +301 -0
  9. codegraph_nav/dependency_graph.py +814 -0
  10. codegraph_nav/domain/__init__.py +20 -0
  11. codegraph_nav/domain/routes.py +337 -0
  12. codegraph_nav/domain/schemas.py +229 -0
  13. codegraph_nav/domain/tags.py +87 -0
  14. codegraph_nav/exporters.py +563 -0
  15. codegraph_nav/go_analyzer.py +273 -0
  16. codegraph_nav/graph/__init__.py +72 -0
  17. codegraph_nav/graph/builder.py +409 -0
  18. codegraph_nav/graph/communities.py +402 -0
  19. codegraph_nav/graph/flows.py +311 -0
  20. codegraph_nav/graph/query.py +380 -0
  21. codegraph_nav/graph/schema.py +266 -0
  22. codegraph_nav/graph/search.py +257 -0
  23. codegraph_nav/graph/store.py +517 -0
  24. codegraph_nav/hints.py +195 -0
  25. codegraph_nav/import_resolver.py +891 -0
  26. codegraph_nav/js_ts_analyzer.py +564 -0
  27. codegraph_nav/line_reader.py +664 -0
  28. codegraph_nav/mcp/__init__.py +39 -0
  29. codegraph_nav/mcp/__main__.py +5 -0
  30. codegraph_nav/mcp/server.py +2228 -0
  31. codegraph_nav/py.typed +2 -0
  32. codegraph_nav/ruby_analyzer.py +259 -0
  33. codegraph_nav/rust_analyzer.py +379 -0
  34. codegraph_nav/token_efficient_renderer.py +743 -0
  35. codegraph_nav/watcher.py +382 -0
  36. codegraph_nav-0.1.0.dist-info/METADATA +487 -0
  37. codegraph_nav-0.1.0.dist-info/RECORD +41 -0
  38. codegraph_nav-0.1.0.dist-info/WHEEL +5 -0
  39. codegraph_nav-0.1.0.dist-info/entry_points.txt +4 -0
  40. codegraph_nav-0.1.0.dist-info/licenses/LICENSE +21 -0
  41. codegraph_nav-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,273 @@
1
+ """Go analyzer using tree-sitter for AST-based symbol extraction.
2
+
3
+ Falls back to regex-based GenericAnalyzer when tree-sitter is not installed.
4
+
5
+ Example:
6
+ >>> from codegraph_nav.go_analyzer import GoAnalyzer
7
+ >>> source = '''
8
+ ... func greet(name string) string {
9
+ ... return "Hello, " + name
10
+ ... }
11
+ ... '''
12
+ >>> analyzer = GoAnalyzer('example.go', source)
13
+ >>> symbols = analyzer.analyze()
14
+ >>> print(symbols[0].name)
15
+ 'greet'
16
+
17
+ Installation:
18
+ To enable AST support, install with the 'ast' extra:
19
+ pip install codegraph-nav[ast]
20
+ """
21
+
22
+ import sys
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from tree_sitter import Node
27
+
28
+ try:
29
+ import tree_sitter_go as ts_go
30
+ from tree_sitter import Language, Parser
31
+
32
+ TREE_SITTER_AVAILABLE = True
33
+ except ImportError:
34
+ TREE_SITTER_AVAILABLE = False
35
+
36
+ from .code_navigator import GenericAnalyzer, Symbol
37
+
38
+
39
+ class GoAnalyzer:
40
+ """Analyzes Go files using tree-sitter for accurate symbol extraction.
41
+
42
+ When tree-sitter is not available, automatically falls back to regex-based
43
+ GenericAnalyzer.
44
+
45
+ Attributes:
46
+ file_path: Path to the file being analyzed.
47
+ source: Source code content.
48
+ symbols: Extracted symbols.
49
+
50
+ Example:
51
+ >>> source = '''
52
+ ... type User struct {
53
+ ... Name string
54
+ ... }
55
+ ...
56
+ ... func (u *User) Greet() string {
57
+ ... return "Hello, " + u.Name
58
+ ... }
59
+ ... '''
60
+ >>> analyzer = GoAnalyzer('user.go', source)
61
+ >>> symbols = analyzer.analyze()
62
+ >>> print([s.name for s in symbols])
63
+ ['User', 'Greet']
64
+ """
65
+
66
+ def __init__(self, file_path: str, source: str):
67
+ self.file_path = file_path
68
+ self.source = source
69
+ self.lines = source.split("\n")
70
+ self.symbols: list[Symbol] = []
71
+
72
+ def analyze(self) -> list[Symbol]:
73
+ """Parse and analyze the file.
74
+
75
+ Returns:
76
+ List of Symbol objects found in the file.
77
+ """
78
+ if not TREE_SITTER_AVAILABLE:
79
+ fallback = GenericAnalyzer(self.file_path, self.source, "go")
80
+ return fallback.analyze()
81
+
82
+ try:
83
+ parser = Parser(Language(ts_go.language()))
84
+ tree = parser.parse(bytes(self.source, "utf-8"))
85
+ self._visit_node(tree.root_node)
86
+ except Exception as e:
87
+ print(f"tree-sitter error in {self.file_path}: {e}", file=sys.stderr)
88
+ fallback = GenericAnalyzer(self.file_path, self.source, "go")
89
+ return fallback.analyze()
90
+
91
+ return self.symbols
92
+
93
+ def _visit_node(self, node: "Node") -> None:
94
+ """Recursively visit AST nodes and extract symbols."""
95
+ node_type = node.type
96
+
97
+ if node_type == "function_declaration":
98
+ self._extract_function(node)
99
+ elif node_type == "method_declaration":
100
+ self._extract_method(node)
101
+ elif node_type == "type_declaration":
102
+ for child in node.children:
103
+ if child.type == "type_spec":
104
+ self._extract_type_spec(child)
105
+ elif node_type == "const_declaration":
106
+ self._extract_const(node)
107
+
108
+ for child in node.children:
109
+ self._visit_node(child)
110
+
111
+ def _get_node_text(self, node: "Node") -> str:
112
+ return self.source[node.start_byte : node.end_byte]
113
+
114
+ def _get_child_by_type(self, node: "Node", type_name: str) -> "Node | None":
115
+ for child in node.children:
116
+ if child.type == type_name:
117
+ return child
118
+ return None
119
+
120
+ def _extract_function(self, node: "Node") -> None:
121
+ """Extract a function declaration."""
122
+ name_node = self._get_child_by_type(node, "identifier")
123
+ if not name_node:
124
+ return
125
+
126
+ name = self._get_node_text(name_node)
127
+
128
+ # Get type parameters (generics)
129
+ type_params = ""
130
+ tp_node = self._get_child_by_type(node, "type_parameter_list")
131
+ if tp_node:
132
+ type_params = self._get_node_text(tp_node)
133
+
134
+ # Get parameters
135
+ params = ""
136
+ for child in node.children:
137
+ if child.type == "parameter_list":
138
+ params = self._get_node_text(child)
139
+ break
140
+
141
+ # Get return type
142
+ result = ""
143
+ for child in node.children:
144
+ if child.type in (
145
+ "type_identifier",
146
+ "slice_type",
147
+ "pointer_type",
148
+ "qualified_type",
149
+ "map_type",
150
+ "parameter_list",
151
+ ):
152
+ # Skip the first parameter_list (that's the params)
153
+ if child.type == "parameter_list" and child == node.children[2]:
154
+ continue
155
+ if child.start_byte > (node.children[2].end_byte if len(node.children) > 2 else 0):
156
+ result = " " + self._get_node_text(child)
157
+ break
158
+
159
+ signature = f"func {name}{type_params}{params}{result}"
160
+
161
+ self.symbols.append(
162
+ Symbol(
163
+ name=name,
164
+ type="function",
165
+ file_path=self.file_path,
166
+ line_start=node.start_point[0] + 1,
167
+ line_end=node.end_point[0] + 1,
168
+ signature=signature[:100],
169
+ )
170
+ )
171
+
172
+ def _extract_method(self, node: "Node") -> None:
173
+ """Extract a method declaration with receiver."""
174
+ # Get method name (field_identifier)
175
+ name_node = self._get_child_by_type(node, "field_identifier")
176
+ if not name_node:
177
+ return
178
+
179
+ name = self._get_node_text(name_node)
180
+
181
+ # Get receiver type (parent)
182
+ parent = None
183
+ param_lists = [c for c in node.children if c.type == "parameter_list"]
184
+ if param_lists:
185
+ receiver_list = param_lists[0]
186
+ for child in receiver_list.children:
187
+ if child.type == "parameter_declaration":
188
+ for rc in child.children:
189
+ if rc.type == "pointer_type":
190
+ # *User -> User
191
+ for pt_child in rc.children:
192
+ if pt_child.type == "type_identifier":
193
+ parent = self._get_node_text(pt_child)
194
+ elif rc.type == "type_identifier":
195
+ parent = self._get_node_text(rc)
196
+
197
+ # Get parameters (second parameter_list)
198
+ params = ""
199
+ if len(param_lists) > 1:
200
+ params = self._get_node_text(param_lists[1])
201
+
202
+ receiver_str = f"({self._get_node_text(param_lists[0])})" if param_lists else ""
203
+ signature = f"func {receiver_str} {name}{params}"
204
+
205
+ self.symbols.append(
206
+ Symbol(
207
+ name=name,
208
+ type="method",
209
+ file_path=self.file_path,
210
+ line_start=node.start_point[0] + 1,
211
+ line_end=node.end_point[0] + 1,
212
+ signature=signature[:100],
213
+ parent=parent,
214
+ )
215
+ )
216
+
217
+ def _extract_type_spec(self, node: "Node") -> None:
218
+ """Extract a type specification (struct, interface, or alias)."""
219
+ name_node = self._get_child_by_type(node, "type_identifier")
220
+ if not name_node:
221
+ return
222
+
223
+ name = self._get_node_text(name_node)
224
+
225
+ # Determine type kind
226
+ struct_node = self._get_child_by_type(node, "struct_type")
227
+ iface_node = self._get_child_by_type(node, "interface_type")
228
+
229
+ if struct_node:
230
+ symbol_type = "struct"
231
+ signature = f"type {name} struct"
232
+ elif iface_node:
233
+ symbol_type = "interface"
234
+ signature = f"type {name} interface"
235
+ else:
236
+ symbol_type = "type"
237
+ # Get the aliased type
238
+ for child in node.children:
239
+ if child.type == "type_identifier" and child != name_node:
240
+ signature = f"type {name} {self._get_node_text(child)}"
241
+ break
242
+ else:
243
+ signature = f"type {name}"
244
+
245
+ self.symbols.append(
246
+ Symbol(
247
+ name=name,
248
+ type=symbol_type,
249
+ file_path=self.file_path,
250
+ line_start=node.start_point[0] + 1,
251
+ line_end=node.end_point[0] + 1,
252
+ signature=signature[:100],
253
+ )
254
+ )
255
+
256
+ def _extract_const(self, node: "Node") -> None:
257
+ """Extract const declarations."""
258
+ for child in node.children:
259
+ if child.type == "const_spec":
260
+ for spec_child in child.children:
261
+ if spec_child.type == "identifier":
262
+ name = self._get_node_text(spec_child)
263
+ self.symbols.append(
264
+ Symbol(
265
+ name=name,
266
+ type="const",
267
+ file_path=self.file_path,
268
+ line_start=child.start_point[0] + 1,
269
+ line_end=child.end_point[0] + 1,
270
+ signature=self._get_node_text(child).strip()[:100],
271
+ )
272
+ )
273
+ break
@@ -0,0 +1,72 @@
1
+ """Graph intelligence layer for codegraph-nav.
2
+
3
+ Optional SQLite-backed graph for advanced analysis: blast radius,
4
+ execution flows, risk scoring, and hybrid FTS5 search.
5
+
6
+ Usage:
7
+ from codegraph_nav.graph import GraphStore, GraphBuilder
8
+
9
+ store = GraphStore(".codegraph.db")
10
+ builder = GraphBuilder(store)
11
+ stats = builder.build_from_code_map(code_map)
12
+ """
13
+
14
+ from .builder import GraphBuilder
15
+ from .flows import (
16
+ compute_criticality,
17
+ detect_entry_points,
18
+ format_flow_minimal,
19
+ format_flows_minimal,
20
+ store_flows,
21
+ trace_flows,
22
+ )
23
+ from .query import (
24
+ detect_changes,
25
+ format_blast_radius_minimal,
26
+ format_changes_minimal,
27
+ get_blast_radius,
28
+ )
29
+ from .schema import (
30
+ EDGE_KINDS,
31
+ MAX_BFS_DEPTH,
32
+ MAX_IMPACT_DEPTH,
33
+ MAX_IMPACT_NODES,
34
+ NODE_KINDS,
35
+ SCHEMA_VERSION,
36
+ SECURITY_KEYWORDS,
37
+ make_qualified_name,
38
+ )
39
+ from .search import (
40
+ format_search_results_minimal,
41
+ hybrid_search,
42
+ rebuild_fts_index,
43
+ rrf_merge,
44
+ )
45
+ from .store import GraphStore
46
+
47
+ __all__ = [
48
+ "GraphStore",
49
+ "GraphBuilder",
50
+ "get_blast_radius",
51
+ "format_blast_radius_minimal",
52
+ "detect_changes",
53
+ "format_changes_minimal",
54
+ "detect_entry_points",
55
+ "trace_flows",
56
+ "store_flows",
57
+ "compute_criticality",
58
+ "format_flow_minimal",
59
+ "format_flows_minimal",
60
+ "hybrid_search",
61
+ "rebuild_fts_index",
62
+ "rrf_merge",
63
+ "format_search_results_minimal",
64
+ "make_qualified_name",
65
+ "SCHEMA_VERSION",
66
+ "EDGE_KINDS",
67
+ "NODE_KINDS",
68
+ "SECURITY_KEYWORDS",
69
+ "MAX_IMPACT_DEPTH",
70
+ "MAX_IMPACT_NODES",
71
+ "MAX_BFS_DEPTH",
72
+ ]