codegraph-py 1.0.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 (44) hide show
  1. codegraph/__init__.py +12 -0
  2. codegraph/__main__.py +6 -0
  3. codegraph/cli.py +911 -0
  4. codegraph/codegraph.py +618 -0
  5. codegraph/context/__init__.py +216 -0
  6. codegraph/db/__init__.py +11 -0
  7. codegraph/db/connection.py +163 -0
  8. codegraph/db/queries.py +752 -0
  9. codegraph/db/schema.sql +151 -0
  10. codegraph/directory.py +160 -0
  11. codegraph/errors.py +101 -0
  12. codegraph/extraction/__init__.py +956 -0
  13. codegraph/extraction/languages/__init__.py +64 -0
  14. codegraph/extraction/languages/base.py +132 -0
  15. codegraph/extraction/languages/c_cfg.py +53 -0
  16. codegraph/extraction/languages/cpp_cfg.py +60 -0
  17. codegraph/extraction/languages/dart_cfg.py +53 -0
  18. codegraph/extraction/languages/go_cfg.py +70 -0
  19. codegraph/extraction/languages/java_cfg.py +62 -0
  20. codegraph/extraction/languages/javascript_cfg.py +84 -0
  21. codegraph/extraction/languages/kotlin_cfg.py +52 -0
  22. codegraph/extraction/languages/lua_cfg.py +65 -0
  23. codegraph/extraction/languages/python_cfg.py +75 -0
  24. codegraph/extraction/languages/ruby_cfg.py +48 -0
  25. codegraph/extraction/languages/rust_cfg.py +68 -0
  26. codegraph/extraction/languages/scala_cfg.py +59 -0
  27. codegraph/extraction/languages/swift_cfg.py +64 -0
  28. codegraph/extraction/languages/typescript_cfg.py +89 -0
  29. codegraph/extraction/tree_sitter_extractor.py +689 -0
  30. codegraph/graph/__init__.py +685 -0
  31. codegraph/installer/__init__.py +6 -0
  32. codegraph/mcp/__init__.py +668 -0
  33. codegraph/project_config.py +191 -0
  34. codegraph/resolution/__init__.py +337 -0
  35. codegraph/search/__init__.py +653 -0
  36. codegraph/sync/__init__.py +204 -0
  37. codegraph/types.py +334 -0
  38. codegraph/ui/__init__.py +11 -0
  39. codegraph/utils.py +218 -0
  40. codegraph_py-1.0.0.dist-info/METADATA +238 -0
  41. codegraph_py-1.0.0.dist-info/RECORD +44 -0
  42. codegraph_py-1.0.0.dist-info/WHEEL +5 -0
  43. codegraph_py-1.0.0.dist-info/entry_points.txt +2 -0
  44. codegraph_py-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,204 @@
1
+ """
2
+ CodeGraph File Synchronization
3
+
4
+ File watcher for auto-syncing the graph on file changes.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import time
11
+ import threading
12
+ from typing import Callable, List, Optional, Set, Dict, Any
13
+ from dataclasses import dataclass, field
14
+
15
+
16
+ @dataclass
17
+ class PendingFile:
18
+ """A file with pending changes."""
19
+ path: str
20
+ modified_at: float
21
+ change_type: str # 'added', 'modified', 'removed'
22
+
23
+
24
+ @dataclass
25
+ class WatchOptions:
26
+ """Options for the file watcher."""
27
+ debounce_ms: int = 2000
28
+ ignore_patterns: Optional[List[str]] = None
29
+
30
+
31
+ class LockUnavailableError(Exception):
32
+ """Raised when a lock cannot be acquired."""
33
+ pass
34
+
35
+
36
+ class FileWatcher:
37
+ """Watches a project directory for file changes using polling."""
38
+
39
+ def __init__(self, project_root: str,
40
+ on_change: Optional[Callable[[List[PendingFile]], None]] = None,
41
+ options: Optional[WatchOptions] = None):
42
+ self._project_root = project_root
43
+ self._on_change = on_change
44
+ self._options = options or WatchOptions()
45
+ self._running = False
46
+ self._thread: Optional[threading.Thread] = None
47
+ self._lock = threading.Lock()
48
+ self._known_files: Dict[str, float] = {}
49
+ self._timer: Optional[threading.Timer] = None
50
+ self._pending: List[PendingFile] = []
51
+
52
+ def start(self) -> None:
53
+ """Start watching for file changes."""
54
+ if self._running:
55
+ return
56
+ self._running = True
57
+
58
+ # Snapshot current state
59
+ self._scan_files()
60
+
61
+ self._thread = threading.Thread(target=self._watch_loop, daemon=True)
62
+ self._thread.start()
63
+
64
+ def stop(self) -> None:
65
+ """Stop watching."""
66
+ self._running = False
67
+ if self._timer:
68
+ self._timer.cancel()
69
+ self._timer = None
70
+
71
+ def _scan_files(self) -> None:
72
+ """Scan project files and record their mtimes."""
73
+ self._known_files.clear()
74
+ for root, dirs, files in os.walk(self._project_root):
75
+ # Skip hidden directories and .codegraph
76
+ dirs[:] = [d for d in dirs
77
+ if not d.startswith('.') and d != '.codegraph'
78
+ and d != 'node_modules' and d != '__pycache__']
79
+
80
+ for f in files:
81
+ # Skip hidden files
82
+ if f.startswith('.'):
83
+ continue
84
+ filepath = os.path.join(root, f)
85
+ try:
86
+ mtime = os.path.getmtime(filepath)
87
+ relpath = os.path.relpath(filepath, self._project_root)
88
+ self._known_files[relpath.replace('\\', '/')] = mtime
89
+ except OSError:
90
+ pass
91
+
92
+ def _watch_loop(self) -> None:
93
+ """Main watch loop - polls for file changes."""
94
+ while self._running:
95
+ try:
96
+ self._check_changes()
97
+ except Exception:
98
+ pass
99
+ time.sleep(1.0) # Poll every second
100
+
101
+ def _check_changes(self) -> None:
102
+ """Check for file changes and trigger debounced callback."""
103
+ current: Dict[str, float] = {}
104
+ pending: List[PendingFile] = []
105
+
106
+ for root, dirs, files in os.walk(self._project_root):
107
+ dirs[:] = [d for d in dirs
108
+ if not d.startswith('.') and d != '.codegraph'
109
+ and d != 'node_modules' and d != '__pycache__']
110
+
111
+ for f in files:
112
+ if f.startswith('.'):
113
+ continue
114
+ filepath = os.path.join(root, f)
115
+ try:
116
+ mtime = os.path.getmtime(filepath)
117
+ relpath = os.path.relpath(filepath, self._project_root)
118
+ normalized = relpath.replace('\\', '/')
119
+ current[normalized] = mtime
120
+ except OSError:
121
+ pass
122
+
123
+ # Check for added/modified
124
+ for path, mtime in current.items():
125
+ if path not in self._known_files:
126
+ pending.append(PendingFile(path, mtime, 'added'))
127
+ elif self._known_files[path] != mtime:
128
+ pending.append(PendingFile(path, mtime, 'modified'))
129
+
130
+ # Check for removed
131
+ for path in self._known_files:
132
+ if path not in current:
133
+ pending.append(PendingFile(path, time.time(), 'removed'))
134
+
135
+ if pending:
136
+ self._known_files = current
137
+ self._debounce_callback(pending)
138
+
139
+ def _debounce_callback(self, pending: List[PendingFile]) -> None:
140
+ """Debounce and fire change callback."""
141
+ if self._timer:
142
+ self._timer.cancel()
143
+
144
+ self._pending.extend(pending)
145
+
146
+ self._timer = threading.Timer(
147
+ self._options.debounce_ms / 1000.0,
148
+ self._fire_callback
149
+ )
150
+ self._timer.daemon = True
151
+ self._timer.start()
152
+
153
+ def _fire_callback(self) -> None:
154
+ """Fire the change callback with pending files."""
155
+ if self._pending and self._on_change:
156
+ try:
157
+ self._on_change(self._pending)
158
+ except Exception:
159
+ pass
160
+ self._pending = []
161
+
162
+
163
+ class LockFile:
164
+ """Simple file-based lock for cross-process synchronization."""
165
+
166
+ def __init__(self, lock_path: str):
167
+ self.lock_path = lock_path
168
+ self._fd = None
169
+
170
+ def acquire(self, timeout: float = 5.0) -> bool:
171
+ """Acquire the lock."""
172
+ deadline = time.time() + timeout
173
+ while time.time() < deadline:
174
+ try:
175
+ self._fd = os.open(
176
+ self.lock_path,
177
+ os.O_CREAT | os.O_EXCL | os.O_RDWR,
178
+ 0o644
179
+ )
180
+ os.write(self._fd, str(os.getpid()).encode())
181
+ return True
182
+ except OSError:
183
+ time.sleep(0.1)
184
+ return False
185
+
186
+ def release(self) -> None:
187
+ """Release the lock."""
188
+ if self._fd is not None:
189
+ try:
190
+ os.close(self._fd)
191
+ except OSError:
192
+ pass
193
+ self._fd = None
194
+ try:
195
+ os.unlink(self.lock_path)
196
+ except OSError:
197
+ pass
198
+
199
+ def __enter__(self):
200
+ self.acquire()
201
+ return self
202
+
203
+ def __exit__(self, *args):
204
+ self.release()
codegraph/types.py ADDED
@@ -0,0 +1,334 @@
1
+ """
2
+ CodeGraph Type Definitions
3
+
4
+ Core types for the semantic knowledge graph system.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import dataclasses
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any, Dict, List, Optional
13
+
14
+
15
+ # =============================================================================
16
+ # Enums & Constants
17
+ # =============================================================================
18
+
19
+ NODE_KINDS = [
20
+ 'file', 'module', 'class', 'struct', 'interface', 'trait', 'protocol',
21
+ 'function', 'method', 'property', 'field', 'variable', 'constant',
22
+ 'enum', 'enum_member', 'type_alias', 'namespace', 'parameter',
23
+ 'import', 'export', 'route', 'component',
24
+ ]
25
+
26
+ EDGE_KINDS = [
27
+ 'contains', 'calls', 'imports', 'exports', 'extends', 'implements',
28
+ 'references', 'type_of', 'returns', 'instantiates', 'overrides', 'decorates',
29
+ ]
30
+
31
+ LANGUAGES = [
32
+ 'typescript', 'javascript', 'tsx', 'jsx', 'arkts',
33
+ 'python', 'go', 'rust', 'java', 'c', 'cpp', 'csharp', 'razor',
34
+ 'php', 'ruby', 'swift', 'kotlin', 'dart', 'svelte', 'vue', 'astro',
35
+ 'liquid', 'pascal', 'scala', 'lua', 'luau', 'objc', 'r', 'solidity',
36
+ 'nix', 'yaml', 'twig', 'xml', 'properties', 'cfml', 'cfscript',
37
+ 'cfquery', 'cobol', 'vbnet', 'erlang', 'terraform', 'unknown',
38
+ ]
39
+
40
+
41
+ class NodeKind(str, Enum):
42
+ """Types of nodes in the knowledge graph."""
43
+ FILE = 'file'
44
+ MODULE = 'module'
45
+ CLASS = 'class'
46
+ STRUCT = 'struct'
47
+ INTERFACE = 'interface'
48
+ TRAIT = 'trait'
49
+ PROTOCOL = 'protocol'
50
+ FUNCTION = 'function'
51
+ METHOD = 'method'
52
+ PROPERTY = 'property'
53
+ FIELD = 'field'
54
+ VARIABLE = 'variable'
55
+ CONSTANT = 'constant'
56
+ ENUM = 'enum'
57
+ ENUM_MEMBER = 'enum_member'
58
+ TYPE_ALIAS = 'type_alias'
59
+ NAMESPACE = 'namespace'
60
+ PARAMETER = 'parameter'
61
+ IMPORT = 'import'
62
+ EXPORT = 'export'
63
+ ROUTE = 'route'
64
+ COMPONENT = 'component'
65
+
66
+
67
+ class EdgeKind(str, Enum):
68
+ """Types of edges (relationships) between nodes."""
69
+ CONTAINS = 'contains'
70
+ CALLS = 'calls'
71
+ IMPORTS = 'imports'
72
+ EXPORTS = 'exports'
73
+ EXTENDS = 'extends'
74
+ IMPLEMENTS = 'implements'
75
+ REFERENCES = 'references'
76
+ TYPE_OF = 'type_of'
77
+ RETURNS = 'returns'
78
+ INSTANTIATES = 'instantiates'
79
+ OVERRIDES = 'overrides'
80
+ DECORATES = 'decorates'
81
+
82
+
83
+ class Language(str, Enum):
84
+ """Supported programming languages."""
85
+ TYPESCRIPT = 'typescript'
86
+ JAVASCRIPT = 'javascript'
87
+ TSX = 'tsx'
88
+ JSX = 'jsx'
89
+ ARKTS = 'arkts'
90
+ PYTHON = 'python'
91
+ GO = 'go'
92
+ RUST = 'rust'
93
+ JAVA = 'java'
94
+ C = 'c'
95
+ CPP = 'cpp'
96
+ CSHARP = 'csharp'
97
+ RAZOR = 'razor'
98
+ PHP = 'php'
99
+ RUBY = 'ruby'
100
+ SWIFT = 'swift'
101
+ KOTLIN = 'kotlin'
102
+ DART = 'dart'
103
+ SVELTE = 'svelte'
104
+ VUE = 'vue'
105
+ ASTRO = 'astro'
106
+ LIQUID = 'liquid'
107
+ PASCAL = 'pascal'
108
+ SCALA = 'scala'
109
+ LUA = 'lua'
110
+ LUAU = 'luau'
111
+ OBJC = 'objc'
112
+ R = 'r'
113
+ SOLIDITY = 'solidity'
114
+ NIX = 'nix'
115
+ TERRAFORM = 'terraform'
116
+ UNKNOWN = 'unknown'
117
+
118
+
119
+ # =============================================================================
120
+ # Core Graph Types
121
+ # =============================================================================
122
+
123
+ @dataclass
124
+ class Node:
125
+ """A node in the knowledge graph representing a code symbol."""
126
+ id: str
127
+ kind: str
128
+ name: str
129
+ qualified_name: str
130
+ file_path: str
131
+ language: str
132
+ start_line: int
133
+ end_line: int
134
+ start_column: int
135
+ end_column: int
136
+ docstring: Optional[str] = None
137
+ signature: Optional[str] = None
138
+ visibility: Optional[str] = None
139
+ is_exported: bool = False
140
+ is_async: bool = False
141
+ is_static: bool = False
142
+ is_abstract: bool = False
143
+ decorators: Optional[List[str]] = None
144
+ type_parameters: Optional[List[str]] = None
145
+ return_type: Optional[str] = None
146
+ updated_at: int = 0
147
+
148
+
149
+ @dataclass
150
+ class Edge:
151
+ """An edge representing a relationship between two nodes."""
152
+ id: Optional[int] = None
153
+ source: str = ''
154
+ target: str = ''
155
+ kind: str = ''
156
+ metadata: Optional[Dict[str, Any]] = None
157
+ line: Optional[int] = None
158
+ column: Optional[int] = None
159
+ provenance: Optional[str] = None
160
+
161
+
162
+ @dataclass
163
+ class FileRecord:
164
+ """Metadata about a tracked file."""
165
+ path: str
166
+ content_hash: str
167
+ language: str
168
+ size: int
169
+ modified_at: int
170
+ indexed_at: int
171
+ node_count: int = 0
172
+ errors: Optional[List[ExtractionError]] = None
173
+
174
+
175
+ @dataclass
176
+ class ExtractionError:
177
+ """Error during code extraction."""
178
+ message: str
179
+ file_path: Optional[str] = None
180
+ line: Optional[int] = None
181
+ column: Optional[int] = None
182
+ severity: str = 'error' # 'error' | 'warning'
183
+ code: Optional[str] = None
184
+
185
+
186
+ @dataclass
187
+ class UnresolvedReference:
188
+ """A reference that couldn't be resolved during extraction."""
189
+ id: Optional[int] = None
190
+ from_node_id: str = ''
191
+ reference_name: str = ''
192
+ reference_kind: str = ''
193
+ line: int = 0
194
+ column: int = 0
195
+ file_path: Optional[str] = None
196
+ language: Optional[str] = None
197
+ candidates: Optional[List[str]] = None
198
+
199
+
200
+ @dataclass
201
+ class ExtractionResult:
202
+ """Result from parsing a source file."""
203
+ nodes: List[Node] = field(default_factory=list)
204
+ edges: List[Edge] = field(default_factory=list)
205
+ unresolved_references: List[UnresolvedReference] = field(default_factory=list)
206
+ errors: List[ExtractionError] = field(default_factory=list)
207
+ duration_ms: float = 0.0
208
+
209
+
210
+ # =============================================================================
211
+ # Query Types
212
+ # =============================================================================
213
+
214
+ @dataclass
215
+ class Subgraph:
216
+ """A subgraph containing a subset of the knowledge graph."""
217
+ nodes: Dict[str, Node] = field(default_factory=dict)
218
+ edges: List[Edge] = field(default_factory=list)
219
+ roots: List[str] = field(default_factory=list)
220
+ confidence: Optional[str] = None # 'high' | 'low'
221
+
222
+
223
+ @dataclass
224
+ class TraversalOptions:
225
+ """Options for graph traversal."""
226
+ max_depth: Optional[int] = None
227
+ edge_kinds: Optional[List[str]] = None
228
+ node_kinds: Optional[List[str]] = None
229
+ direction: str = 'outgoing' # 'outgoing' | 'incoming' | 'both'
230
+ limit: Optional[int] = None
231
+ include_start: bool = True
232
+
233
+
234
+ @dataclass
235
+ class SearchOptions:
236
+ """Options for searching the graph."""
237
+ kinds: Optional[List[str]] = None
238
+ languages: Optional[List[str]] = None
239
+ include_patterns: Optional[List[str]] = None
240
+ exclude_patterns: Optional[List[str]] = None
241
+ limit: int = 20
242
+ offset: int = 0
243
+ case_sensitive: bool = False
244
+
245
+
246
+ @dataclass
247
+ class SearchResult:
248
+ """A search result with relevance scoring."""
249
+ node: Node
250
+ score: float
251
+ highlights: Optional[List[str]] = None
252
+
253
+
254
+ @dataclass
255
+ class SegmentMatch:
256
+ """A symbol whose name-segments match prose words from a prompt."""
257
+ name: str
258
+ kind: str
259
+ file_path: str
260
+ start_line: int
261
+ matched_words: List[str] = field(default_factory=list)
262
+
263
+
264
+ # =============================================================================
265
+ # Context Types
266
+ # =============================================================================
267
+
268
+ @dataclass
269
+ class Context:
270
+ """Context information for code understanding."""
271
+ focal: Optional[Node] = None
272
+ ancestors: List[Node] = field(default_factory=list)
273
+ children: List[Node] = field(default_factory=list)
274
+ incoming_refs: List[Dict[str, Any]] = field(default_factory=list)
275
+ outgoing_refs: List[Dict[str, Any]] = field(default_factory=list)
276
+ types: List[Node] = field(default_factory=list)
277
+ imports: List[Node] = field(default_factory=list)
278
+
279
+
280
+ @dataclass
281
+ class CodeBlock:
282
+ """A block of code with context."""
283
+ content: str
284
+ file_path: str
285
+ start_line: int
286
+ end_line: int
287
+ language: str
288
+ node: Optional[Node] = None
289
+
290
+
291
+ # =============================================================================
292
+ # Statistics
293
+ # =============================================================================
294
+
295
+ @dataclass
296
+ class GraphStats:
297
+ """Statistics about the knowledge graph."""
298
+ node_count: int = 0
299
+ edge_count: int = 0
300
+ file_count: int = 0
301
+ nodes_by_kind: Dict[str, int] = field(default_factory=dict)
302
+ edges_by_kind: Dict[str, int] = field(default_factory=dict)
303
+ files_by_language: Dict[str, int] = field(default_factory=dict)
304
+ db_size_bytes: int = 0
305
+ last_updated: int = 0
306
+
307
+
308
+ # =============================================================================
309
+ # Task Context Types
310
+ # =============================================================================
311
+
312
+ @dataclass
313
+ class BuildContextOptions:
314
+ """Options for building task context."""
315
+ max_nodes: int = 50
316
+ max_code_blocks: int = 10
317
+ max_code_block_size: int = 2000
318
+ include_code: bool = True
319
+ format: str = 'markdown' # 'markdown' | 'json'
320
+ search_limit: int = 5
321
+ traversal_depth: int = 2
322
+ min_score: float = 0.3
323
+
324
+
325
+ @dataclass
326
+ class TaskContext:
327
+ """Full context for a task, ready for AI agent consumption."""
328
+ query: str
329
+ subgraph: Optional[Subgraph] = None
330
+ entry_points: List[Node] = field(default_factory=list)
331
+ code_blocks: List[CodeBlock] = field(default_factory=list)
332
+ related_files: List[str] = field(default_factory=list)
333
+ summary: str = ''
334
+ stats: Optional[Dict[str, int]] = None
@@ -0,0 +1,11 @@
1
+ """CodeGraph UI Utilities."""
2
+
3
+ # Placeholder for CLI UI helpers (progress bars, glyphs, etc.)
4
+ GLYPHS = {
5
+ 'ok': '✓',
6
+ 'err': '✗',
7
+ 'warn': '⚠',
8
+ 'info': 'ℹ',
9
+ 'dash': '—',
10
+ 'rail': '│',
11
+ }