srcodex 0.2.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 (52) hide show
  1. srcodex/__init__.py +0 -0
  2. srcodex/backend/__init__.py +0 -0
  3. srcodex/backend/chat.py +79 -0
  4. srcodex/backend/main.py +98 -0
  5. srcodex/backend/services/__init__.py +0 -0
  6. srcodex/backend/services/claude_service.py +754 -0
  7. srcodex/backend/services/config_loader.py +113 -0
  8. srcodex/backend/services/file_access_tools.py +279 -0
  9. srcodex/backend/services/file_tree.py +480 -0
  10. srcodex/backend/services/graph_tools.py +874 -0
  11. srcodex/backend/services/logger_setup.py +91 -0
  12. srcodex/backend/services/session_manager.py +81 -0
  13. srcodex/backend/services/status_tracker.py +91 -0
  14. srcodex/cli.py +255 -0
  15. srcodex/core/__init__.py +0 -0
  16. srcodex/core/config.py +113 -0
  17. srcodex/core/logger.py +23 -0
  18. srcodex/indexer/__init__.py +0 -0
  19. srcodex/indexer/cscope_client.py +183 -0
  20. srcodex/indexer/ctags_compat.py +223 -0
  21. srcodex/indexer/ctags_parser.py +456 -0
  22. srcodex/indexer/explorer.py +135 -0
  23. srcodex/indexer/field_access_analyzer.py +436 -0
  24. srcodex/indexer/indexer.py +664 -0
  25. srcodex/indexer/reference_ingestor.py +293 -0
  26. srcodex/indexer/reference_resolver.py +544 -0
  27. srcodex/tui/__init__.py +0 -0
  28. srcodex/tui/app.py +103 -0
  29. srcodex/tui/app.tcss +24 -0
  30. srcodex/tui/components/__init__.py +0 -0
  31. srcodex/tui/components/bars/__init__.py +0 -0
  32. srcodex/tui/components/bars/chat_header.py +48 -0
  33. srcodex/tui/components/bars/code_tab_bar.py +157 -0
  34. srcodex/tui/components/bars/footer_bar.py +128 -0
  35. srcodex/tui/components/bars/left_tab.py +54 -0
  36. srcodex/tui/components/logger.py +57 -0
  37. srcodex/tui/components/panels/__init__.py +0 -0
  38. srcodex/tui/components/panels/chat_panel.py +523 -0
  39. srcodex/tui/components/panels/code_panel.py +229 -0
  40. srcodex/tui/components/panels/side_panel.py +128 -0
  41. srcodex/tui/components/views/__init__.py +0 -0
  42. srcodex/tui/components/views/explorer_view.py +20 -0
  43. srcodex/tui/components/views/search_view.py +148 -0
  44. srcodex/tui/components/widgets/__init__.py +0 -0
  45. srcodex/tui/components/widgets/file_browser.py +16 -0
  46. srcodex/tui/components/widgets/find_box.py +85 -0
  47. srcodex-0.2.0.dist-info/METADATA +170 -0
  48. srcodex-0.2.0.dist-info/RECORD +52 -0
  49. srcodex-0.2.0.dist-info/WHEEL +5 -0
  50. srcodex-0.2.0.dist-info/entry_points.txt +2 -0
  51. srcodex-0.2.0.dist-info/licenses/LICENSE +21 -0
  52. srcodex-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Reference Ingestor - Raw cscope output to database storage
4
+ Ingests cscope query results into raw_references table (untrusted sensor data)
5
+ """
6
+
7
+ import sqlite3
8
+ from pathlib import Path
9
+ from typing import List, Tuple
10
+ from tqdm import tqdm
11
+
12
+ from .cscope_client import CscopeClient
13
+
14
+
15
+ class ReferenceIngestor:
16
+ """Ingests raw cscope query results into raw_references table"""
17
+
18
+ def __init__(self, db_conn: sqlite3.Connection, source_root: Path, cscope_dir: Path):
19
+ """
20
+ Initialize reference ingestor
21
+
22
+ Args:
23
+ db_conn: SQLite database connection (must have raw_references table)
24
+ source_root: Root directory of source code (for path normalization)
25
+ cscope_dir: Directory containing cscope.out
26
+ """
27
+ self.conn = db_conn
28
+ self.source_root = source_root
29
+ self.cscope_client = CscopeClient(str(cscope_dir))
30
+
31
+ def _normalize_path(self, cscope_path: str) -> str:
32
+ """
33
+ Normalize cscope output path to canonical rel_posix format.
34
+ Args:
35
+ cscope_path: Path from cscope output (should be rel_posix from source_root)
36
+ Returns:
37
+ Canonical rel_posix path (relative to source_root)
38
+ """
39
+ path = Path(cscope_path)
40
+
41
+ if path.is_absolute():
42
+ try:
43
+ return path.relative_to(self.source_root).as_posix()
44
+ except ValueError:
45
+ return path.as_posix()
46
+
47
+ # Normalize to POSIX format
48
+ return path.as_posix()
49
+
50
+ def get_all_functions(self) -> List[Tuple[int, str]]:
51
+ """
52
+ Query all function symbols from database (implementations only, not prototypes)
53
+
54
+ Returns:
55
+ List of (symbol_id, function_name) tuples
56
+
57
+ Note: Excludes prototypes to avoid querying cscope multiple times for the same function
58
+ """
59
+ cursor = self.conn.cursor()
60
+ cursor.execute(
61
+ "SELECT id, name FROM symbols WHERE type = 'function' AND kind_raw = 'function' ORDER BY id"
62
+ )
63
+ return cursor.fetchall()
64
+
65
+ def get_all_headers(self) -> List[Tuple[str, str]]:
66
+ """
67
+ Query all header files from database
68
+
69
+ Returns:
70
+ List of (file_path, basename) tuples
71
+ Example: [("power.h", "power.h"), ("common/voltage.h", "voltage.h")]
72
+ """
73
+ cursor = self.conn.cursor()
74
+ cursor.execute(
75
+ "SELECT path FROM files WHERE path LIKE '%.h' ORDER BY path"
76
+ )
77
+ files = cursor.fetchall()
78
+
79
+ # Return (full_path, basename) tuples for cscope queries
80
+ return [(row['path'], Path(row['path']).name) for row in files]
81
+
82
+ def ingest_callees(self, clear_existing: bool = False) -> int:
83
+ """
84
+ Ingest callgraph data: query cscope -2 (callees) for all functions
85
+
86
+ For each function in symbols table:
87
+ - Query cscope find_callees(function_name)
88
+ - Store results in raw_references with query_type='callees'
89
+
90
+ Args:
91
+ clear_existing: If True, delete existing raw_references before ingestion
92
+ Returns:
93
+ Number of raw references inserted
94
+ """
95
+ if clear_existing:
96
+ print("Clearing existing raw_references...")
97
+ self.conn.execute("DELETE FROM raw_references WHERE query_type = 'callees'")
98
+ self.conn.commit()
99
+
100
+ # Get all functions from symbols table
101
+ functions = self.get_all_functions()
102
+ print(f"Found {len(functions)} functions to query")
103
+
104
+ if not functions:
105
+ print("Warning: No functions found in symbols table")
106
+ return 0
107
+
108
+ # Prepare batch insert data
109
+ raw_refs_batch = []
110
+
111
+ # Query cscope for each function with progress bar
112
+ print("Querying cscope for callees...")
113
+ for symbol_id, function_name in tqdm(functions, desc="Ingesting callees"):
114
+ try:
115
+ # Query cscope: find functions called by this function
116
+ results = self.cscope_client.find_callees(function_name)
117
+
118
+ # Convert each Reference to raw_references row
119
+ for ref in results:
120
+ normalized_path = self._normalize_path(ref.file_path)
121
+ raw_refs_batch.append((
122
+ 'callees', # query_type
123
+ function_name, # query_symbol (the function we queried)
124
+ normalized_path, # source_file
125
+ ref.function, # source_function (from cscope output)
126
+ ref.line_number, # line_number
127
+ ref.line_text, # line_text
128
+ ))
129
+
130
+ except Exception as e:
131
+ # Don't fail entire ingestion on single query error
132
+ tqdm.write(f"Warning: Failed to query {function_name}: {e}")
133
+ continue
134
+
135
+ # Batch insert with transaction
136
+ if raw_refs_batch:
137
+ print(f"Inserting {len(raw_refs_batch)} raw references...")
138
+ cursor = self.conn.cursor()
139
+ cursor.executemany(
140
+ """INSERT INTO raw_references
141
+ (query_type, query_symbol, source_file, source_function, line_number, line_text)
142
+ VALUES (?, ?, ?, ?, ?, ?)""",
143
+ raw_refs_batch
144
+ )
145
+ self.conn.commit()
146
+ print(f"Inserted {len(raw_refs_batch)} raw references")
147
+ else:
148
+ print("Warning: No references found")
149
+
150
+ return len(raw_refs_batch)
151
+
152
+ def ingest_callers(self, clear_existing: bool = False) -> int:
153
+ """
154
+ Ingest reverse callgraph data: query cscope -3 (callers) for all functions
155
+
156
+ For each function in symbols table:
157
+ - Query cscope find_callers(function_name)
158
+ - Store results in raw_references with query_type='callers'
159
+
160
+ Args:
161
+ clear_existing: If True, delete existing raw_references before ingestion
162
+ Returns:
163
+ Number of raw references inserted
164
+ """
165
+ if clear_existing:
166
+ print("Clearing existing callers references...")
167
+ self.conn.execute("DELETE FROM raw_references WHERE query_type = 'callers'")
168
+ self.conn.commit()
169
+
170
+ # Get all functions from symbols table
171
+ functions = self.get_all_functions()
172
+ print(f"Found {len(functions)} functions to query for callers")
173
+
174
+ if not functions:
175
+ print("Warning: No functions found in symbols table")
176
+ return 0
177
+
178
+ # Prepare batch insert data
179
+ raw_refs_batch = []
180
+
181
+ # Query cscope for each function with progress bar
182
+ print("Querying cscope for callers...")
183
+ for symbol_id, function_name in tqdm(functions, desc="Ingesting callers"):
184
+ try:
185
+ # Query cscope: find functions that call this function
186
+ results = self.cscope_client.find_callers(function_name)
187
+
188
+ # Convert each Reference to raw_references row
189
+ for ref in results:
190
+ normalized_path = self._normalize_path(ref.file_path)
191
+ raw_refs_batch.append((
192
+ 'callers', # query_type
193
+ function_name, # query_symbol (the function we queried)
194
+ normalized_path, # source_file
195
+ ref.function, # source_function (caller)
196
+ ref.line_number, # line_number
197
+ ref.line_text, # line_text
198
+ ))
199
+
200
+ except Exception as e:
201
+ # Don't fail entire ingestion on single query error
202
+ tqdm.write(f"Warning: Failed to query {function_name}: {e}")
203
+ continue
204
+
205
+ # Batch insert with transaction
206
+ if raw_refs_batch:
207
+ print(f"Inserting {len(raw_refs_batch)} raw references...")
208
+ cursor = self.conn.cursor()
209
+ cursor.executemany(
210
+ """INSERT INTO raw_references
211
+ (query_type, query_symbol, source_file, source_function, line_number, line_text)
212
+ VALUES (?, ?, ?, ?, ?, ?)""",
213
+ raw_refs_batch
214
+ )
215
+ self.conn.commit()
216
+ print(f"Inserted {len(raw_refs_batch)} callers references")
217
+ else:
218
+ print("Warning: No callers references found")
219
+
220
+ return len(raw_refs_batch)
221
+
222
+ def ingest_includes(self, clear_existing: bool = False) -> int:
223
+ """
224
+ Ingest include graph data: query cscope -8 (files including) for all headers
225
+
226
+ For each .h file in files table:
227
+ - Query cscope find_files_including(header_name)
228
+ - Store results in raw_references with query_type='includes'
229
+
230
+ Note: source_function will be "<global>" since includes are file-level
231
+
232
+ Args:
233
+ clear_existing: If True, delete existing includes references before ingestion
234
+ Returns:
235
+ Number of raw references inserted
236
+ """
237
+ if clear_existing:
238
+ print("Clearing existing includes references...")
239
+ self.conn.execute("DELETE FROM raw_references WHERE query_type = 'includes'")
240
+ self.conn.commit()
241
+
242
+ # Get all header files from files table
243
+ headers = self.get_all_headers()
244
+ print(f"Found {len(headers)} header files to query for includes")
245
+
246
+ if not headers:
247
+ print("Warning: No header files found in files table")
248
+ return 0
249
+
250
+ # Prepare batch insert data
251
+ raw_refs_batch = []
252
+
253
+ # Query cscope for each header with progress bar
254
+ print("Querying cscope for includes...")
255
+ for full_path, basename in tqdm(headers, desc="Ingesting includes"):
256
+ try:
257
+ # Query cscope: find files that include this header
258
+ # NOTE: cscope -8 queries by basename, not full path
259
+ results = self.cscope_client.find_files_including(basename)
260
+
261
+ # Convert each Reference to raw_references row
262
+ for ref in results:
263
+ normalized_path = self._normalize_path(ref.file_path)
264
+ raw_refs_batch.append((
265
+ 'includes', # query_type
266
+ basename, # query_symbol (the header we queried)
267
+ normalized_path, # source_file (file that includes the header)
268
+ '<global>', # source_function (includes are file-level)
269
+ ref.line_number, # line_number
270
+ ref.line_text, # line_text (the #include directive)
271
+ ))
272
+
273
+ except Exception as e:
274
+ # Don't fail entire ingestion on single query error
275
+ tqdm.write(f"Warning: Failed to query {basename}: {e}")
276
+ continue
277
+
278
+ # Batch insert with transaction
279
+ if raw_refs_batch:
280
+ print(f"Inserting {len(raw_refs_batch)} raw references...")
281
+ cursor = self.conn.cursor()
282
+ cursor.executemany(
283
+ """INSERT INTO raw_references
284
+ (query_type, query_symbol, source_file, source_function, line_number, line_text)
285
+ VALUES (?, ?, ?, ?, ?, ?)""",
286
+ raw_refs_batch
287
+ )
288
+ self.conn.commit()
289
+ print(f"Inserted {len(raw_refs_batch)} includes references")
290
+ else:
291
+ print("Warning: No includes references found")
292
+
293
+ return len(raw_refs_batch)