roma-debug 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.
- roma_debug/__init__.py +3 -0
- roma_debug/config.py +79 -0
- roma_debug/core/__init__.py +5 -0
- roma_debug/core/engine.py +423 -0
- roma_debug/core/models.py +313 -0
- roma_debug/main.py +753 -0
- roma_debug/parsers/__init__.py +21 -0
- roma_debug/parsers/base.py +189 -0
- roma_debug/parsers/python_ast_parser.py +268 -0
- roma_debug/parsers/registry.py +196 -0
- roma_debug/parsers/traceback_patterns.py +314 -0
- roma_debug/parsers/treesitter_parser.py +598 -0
- roma_debug/prompts.py +153 -0
- roma_debug/server.py +247 -0
- roma_debug/tracing/__init__.py +28 -0
- roma_debug/tracing/call_chain.py +278 -0
- roma_debug/tracing/context_builder.py +672 -0
- roma_debug/tracing/dependency_graph.py +298 -0
- roma_debug/tracing/error_analyzer.py +399 -0
- roma_debug/tracing/import_resolver.py +315 -0
- roma_debug/tracing/project_scanner.py +569 -0
- roma_debug/utils/__init__.py +5 -0
- roma_debug/utils/context.py +422 -0
- roma_debug-0.1.0.dist-info/METADATA +34 -0
- roma_debug-0.1.0.dist-info/RECORD +36 -0
- roma_debug-0.1.0.dist-info/WHEEL +5 -0
- roma_debug-0.1.0.dist-info/entry_points.txt +2 -0
- roma_debug-0.1.0.dist-info/licenses/LICENSE +201 -0
- roma_debug-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_context.py +208 -0
- tests/test_engine.py +296 -0
- tests/test_parsers.py +534 -0
- tests/test_project_scanner.py +275 -0
- tests/test_traceback_patterns.py +222 -0
- tests/test_tracing.py +296 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""Core data models for ROMA Debug V2.
|
|
2
|
+
|
|
3
|
+
This module provides enhanced data structures for multi-language support
|
|
4
|
+
and deep debugging capabilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Optional, List, Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Language(Enum):
|
|
13
|
+
"""Supported programming languages."""
|
|
14
|
+
PYTHON = "python"
|
|
15
|
+
JAVASCRIPT = "javascript"
|
|
16
|
+
TYPESCRIPT = "typescript"
|
|
17
|
+
GO = "go"
|
|
18
|
+
RUST = "rust"
|
|
19
|
+
JAVA = "java"
|
|
20
|
+
C = "c"
|
|
21
|
+
CPP = "cpp"
|
|
22
|
+
CSHARP = "csharp"
|
|
23
|
+
RUBY = "ruby"
|
|
24
|
+
PHP = "php"
|
|
25
|
+
UNKNOWN = "unknown"
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_extension(cls, ext: str) -> "Language":
|
|
29
|
+
"""Get language from file extension.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
ext: File extension (with or without leading dot)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Language enum value
|
|
36
|
+
"""
|
|
37
|
+
ext = ext.lower().lstrip(".")
|
|
38
|
+
mapping = {
|
|
39
|
+
"py": cls.PYTHON,
|
|
40
|
+
"pyw": cls.PYTHON,
|
|
41
|
+
"pyi": cls.PYTHON,
|
|
42
|
+
"js": cls.JAVASCRIPT,
|
|
43
|
+
"mjs": cls.JAVASCRIPT,
|
|
44
|
+
"cjs": cls.JAVASCRIPT,
|
|
45
|
+
"jsx": cls.JAVASCRIPT,
|
|
46
|
+
"ts": cls.TYPESCRIPT,
|
|
47
|
+
"tsx": cls.TYPESCRIPT,
|
|
48
|
+
"mts": cls.TYPESCRIPT,
|
|
49
|
+
"cts": cls.TYPESCRIPT,
|
|
50
|
+
"go": cls.GO,
|
|
51
|
+
"rs": cls.RUST,
|
|
52
|
+
"java": cls.JAVA,
|
|
53
|
+
"c": cls.C,
|
|
54
|
+
"h": cls.C,
|
|
55
|
+
"cpp": cls.CPP,
|
|
56
|
+
"cc": cls.CPP,
|
|
57
|
+
"cxx": cls.CPP,
|
|
58
|
+
"hpp": cls.CPP,
|
|
59
|
+
"hxx": cls.CPP,
|
|
60
|
+
"cs": cls.CSHARP,
|
|
61
|
+
"rb": cls.RUBY,
|
|
62
|
+
"php": cls.PHP,
|
|
63
|
+
}
|
|
64
|
+
return mapping.get(ext, cls.UNKNOWN)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Symbol:
|
|
69
|
+
"""A code symbol (function, method, class, etc.).
|
|
70
|
+
|
|
71
|
+
Represents a named code construct that can contain the error line.
|
|
72
|
+
"""
|
|
73
|
+
name: str
|
|
74
|
+
kind: str # 'function', 'method', 'class', 'module', etc.
|
|
75
|
+
start_line: int
|
|
76
|
+
end_line: int
|
|
77
|
+
start_col: int = 0
|
|
78
|
+
end_col: int = 0
|
|
79
|
+
parent: Optional["Symbol"] = None
|
|
80
|
+
children: List["Symbol"] = field(default_factory=list)
|
|
81
|
+
docstring: Optional[str] = None
|
|
82
|
+
decorators: List[str] = field(default_factory=list)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def qualified_name(self) -> str:
|
|
86
|
+
"""Get fully qualified name including parent chain."""
|
|
87
|
+
if self.parent:
|
|
88
|
+
return f"{self.parent.qualified_name}.{self.name}"
|
|
89
|
+
return self.name
|
|
90
|
+
|
|
91
|
+
def contains_line(self, line_number: int) -> bool:
|
|
92
|
+
"""Check if this symbol contains the given line number."""
|
|
93
|
+
return self.start_line <= line_number <= self.end_line
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class Import:
|
|
98
|
+
"""Represents an import statement.
|
|
99
|
+
|
|
100
|
+
Tracks both the import syntax and resolved file path.
|
|
101
|
+
"""
|
|
102
|
+
module_name: str # e.g., 'os.path', 'lodash', './utils'
|
|
103
|
+
alias: Optional[str] = None # e.g., 'np' for 'import numpy as np'
|
|
104
|
+
imported_names: List[str] = field(default_factory=list) # e.g., ['join', 'dirname']
|
|
105
|
+
is_relative: bool = False
|
|
106
|
+
relative_level: int = 0 # Number of dots for relative imports
|
|
107
|
+
line_number: int = 0
|
|
108
|
+
resolved_path: Optional[str] = None # Actual file path after resolution
|
|
109
|
+
language: Language = Language.UNKNOWN
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def full_import_string(self) -> str:
|
|
113
|
+
"""Reconstruct the import statement."""
|
|
114
|
+
if self.language == Language.PYTHON:
|
|
115
|
+
if self.imported_names:
|
|
116
|
+
prefix = "." * self.relative_level if self.is_relative else ""
|
|
117
|
+
return f"from {prefix}{self.module_name} import {', '.join(self.imported_names)}"
|
|
118
|
+
elif self.alias:
|
|
119
|
+
return f"import {self.module_name} as {self.alias}"
|
|
120
|
+
else:
|
|
121
|
+
return f"import {self.module_name}"
|
|
122
|
+
elif self.language in (Language.JAVASCRIPT, Language.TYPESCRIPT):
|
|
123
|
+
if self.imported_names:
|
|
124
|
+
return f"import {{ {', '.join(self.imported_names)} }} from '{self.module_name}'"
|
|
125
|
+
elif self.alias:
|
|
126
|
+
return f"import {self.alias} from '{self.module_name}'"
|
|
127
|
+
else:
|
|
128
|
+
return f"import '{self.module_name}'"
|
|
129
|
+
elif self.language == Language.GO:
|
|
130
|
+
if self.alias:
|
|
131
|
+
return f'import {self.alias} "{self.module_name}"'
|
|
132
|
+
return f'import "{self.module_name}"'
|
|
133
|
+
return f"import {self.module_name}"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class FileContext:
|
|
138
|
+
"""Extracted context from a source file.
|
|
139
|
+
|
|
140
|
+
This class is backward compatible with the original FileContext
|
|
141
|
+
while adding V2 fields for multi-language support.
|
|
142
|
+
"""
|
|
143
|
+
filepath: str
|
|
144
|
+
line_number: int
|
|
145
|
+
context_type: str # 'ast', 'lines', 'missing', 'treesitter'
|
|
146
|
+
content: str
|
|
147
|
+
function_name: Optional[str] = None
|
|
148
|
+
class_name: Optional[str] = None
|
|
149
|
+
# V2 additions
|
|
150
|
+
language: Language = Language.UNKNOWN
|
|
151
|
+
imports: List[Import] = field(default_factory=list)
|
|
152
|
+
symbol: Optional[Symbol] = None
|
|
153
|
+
raw_source: Optional[str] = None # Full file source for later analysis
|
|
154
|
+
|
|
155
|
+
def to_dict(self) -> dict:
|
|
156
|
+
"""Convert to dictionary (for JSON serialization)."""
|
|
157
|
+
return {
|
|
158
|
+
"filepath": self.filepath,
|
|
159
|
+
"line_number": self.line_number,
|
|
160
|
+
"context_type": self.context_type,
|
|
161
|
+
"content": self.content,
|
|
162
|
+
"function_name": self.function_name,
|
|
163
|
+
"class_name": self.class_name,
|
|
164
|
+
"language": self.language.value,
|
|
165
|
+
"imports": [
|
|
166
|
+
{
|
|
167
|
+
"module_name": imp.module_name,
|
|
168
|
+
"alias": imp.alias,
|
|
169
|
+
"imported_names": imp.imported_names,
|
|
170
|
+
"resolved_path": imp.resolved_path,
|
|
171
|
+
}
|
|
172
|
+
for imp in self.imports
|
|
173
|
+
],
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class TraceFrame:
|
|
179
|
+
"""A single frame from a stack trace.
|
|
180
|
+
|
|
181
|
+
Represents one level in the call stack from an error traceback.
|
|
182
|
+
"""
|
|
183
|
+
filepath: str
|
|
184
|
+
line_number: int
|
|
185
|
+
function_name: Optional[str] = None
|
|
186
|
+
column_number: Optional[int] = None
|
|
187
|
+
code_snippet: Optional[str] = None
|
|
188
|
+
language: Language = Language.UNKNOWN
|
|
189
|
+
|
|
190
|
+
def __str__(self) -> str:
|
|
191
|
+
parts = [f"{self.filepath}:{self.line_number}"]
|
|
192
|
+
if self.function_name:
|
|
193
|
+
parts.append(f" in {self.function_name}")
|
|
194
|
+
return "".join(parts)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class ParsedTraceback:
|
|
199
|
+
"""A fully parsed traceback/stack trace.
|
|
200
|
+
|
|
201
|
+
Contains all frames from the error plus the error message.
|
|
202
|
+
"""
|
|
203
|
+
frames: List[TraceFrame] = field(default_factory=list)
|
|
204
|
+
error_type: Optional[str] = None
|
|
205
|
+
error_message: Optional[str] = None
|
|
206
|
+
language: Language = Language.UNKNOWN
|
|
207
|
+
raw_traceback: str = ""
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def primary_frame(self) -> Optional[TraceFrame]:
|
|
211
|
+
"""Get the primary frame (usually the last one where error occurred)."""
|
|
212
|
+
if self.frames:
|
|
213
|
+
return self.frames[-1]
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def files(self) -> List[str]:
|
|
218
|
+
"""Get list of unique file paths from all frames."""
|
|
219
|
+
seen = set()
|
|
220
|
+
result = []
|
|
221
|
+
for frame in self.frames:
|
|
222
|
+
if frame.filepath not in seen:
|
|
223
|
+
seen.add(frame.filepath)
|
|
224
|
+
result.append(frame.filepath)
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass
|
|
229
|
+
class UpstreamContext:
|
|
230
|
+
"""Context from upstream modules (imports and callers).
|
|
231
|
+
|
|
232
|
+
Used for deep debugging to trace root causes beyond the traceback.
|
|
233
|
+
"""
|
|
234
|
+
file_contexts: List[FileContext] = field(default_factory=list)
|
|
235
|
+
call_chain: List[str] = field(default_factory=list) # ["module_a.func1", "module_b.func2"]
|
|
236
|
+
relevant_definitions: dict = field(default_factory=dict) # {symbol: code}
|
|
237
|
+
dependency_summary: str = ""
|
|
238
|
+
|
|
239
|
+
def to_prompt_text(self) -> str:
|
|
240
|
+
"""Format upstream context for inclusion in AI prompt."""
|
|
241
|
+
parts = []
|
|
242
|
+
|
|
243
|
+
if self.call_chain:
|
|
244
|
+
parts.append("## CALL CHAIN")
|
|
245
|
+
parts.append(" -> ".join(self.call_chain))
|
|
246
|
+
|
|
247
|
+
if self.relevant_definitions:
|
|
248
|
+
parts.append("\n## RELEVANT DEFINITIONS")
|
|
249
|
+
for symbol, code in self.relevant_definitions.items():
|
|
250
|
+
parts.append(f"\n### {symbol}")
|
|
251
|
+
parts.append(code)
|
|
252
|
+
|
|
253
|
+
if self.file_contexts:
|
|
254
|
+
parts.append("\n## UPSTREAM FILE CONTEXTS")
|
|
255
|
+
for ctx in self.file_contexts:
|
|
256
|
+
parts.append(f"\n### {ctx.filepath}")
|
|
257
|
+
parts.append(ctx.content)
|
|
258
|
+
|
|
259
|
+
if self.dependency_summary:
|
|
260
|
+
parts.append("\n## DEPENDENCY SUMMARY")
|
|
261
|
+
parts.append(self.dependency_summary)
|
|
262
|
+
|
|
263
|
+
return "\n".join(parts)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@dataclass
|
|
267
|
+
class AnalysisContext:
|
|
268
|
+
"""Complete context for AI analysis.
|
|
269
|
+
|
|
270
|
+
Combines primary error context with traceback and upstream information.
|
|
271
|
+
"""
|
|
272
|
+
primary_context: FileContext
|
|
273
|
+
traceback_contexts: List[FileContext] = field(default_factory=list)
|
|
274
|
+
upstream_context: Optional[UpstreamContext] = None
|
|
275
|
+
parsed_traceback: Optional[ParsedTraceback] = None
|
|
276
|
+
project_root: Optional[str] = None
|
|
277
|
+
error_analysis: Optional[object] = None # ErrorAnalysis from error_analyzer
|
|
278
|
+
|
|
279
|
+
def to_prompt_text(self) -> str:
|
|
280
|
+
"""Format complete context for AI prompt."""
|
|
281
|
+
parts = []
|
|
282
|
+
|
|
283
|
+
# Primary error context
|
|
284
|
+
parts.append("## PRIMARY ERROR CONTEXT")
|
|
285
|
+
parts.append(f"File: {self.primary_context.filepath}")
|
|
286
|
+
parts.append(f"Line: {self.primary_context.line_number}")
|
|
287
|
+
if self.primary_context.function_name:
|
|
288
|
+
parts.append(f"Function: {self.primary_context.function_name}")
|
|
289
|
+
if self.primary_context.class_name:
|
|
290
|
+
parts.append(f"Class: {self.primary_context.class_name}")
|
|
291
|
+
parts.append(f"Language: {self.primary_context.language.value}")
|
|
292
|
+
parts.append("\n```")
|
|
293
|
+
parts.append(self.primary_context.content)
|
|
294
|
+
parts.append("```")
|
|
295
|
+
|
|
296
|
+
# Traceback contexts
|
|
297
|
+
if self.traceback_contexts:
|
|
298
|
+
parts.append("\n## TRACEBACK CONTEXTS")
|
|
299
|
+
for ctx in self.traceback_contexts:
|
|
300
|
+
if ctx.filepath != self.primary_context.filepath:
|
|
301
|
+
parts.append(f"\n### {ctx.filepath}:{ctx.line_number}")
|
|
302
|
+
if ctx.function_name:
|
|
303
|
+
parts.append(f"Function: {ctx.function_name}")
|
|
304
|
+
parts.append("```")
|
|
305
|
+
parts.append(ctx.content)
|
|
306
|
+
parts.append("```")
|
|
307
|
+
|
|
308
|
+
# Upstream context for deep debugging
|
|
309
|
+
if self.upstream_context:
|
|
310
|
+
parts.append("\n## UPSTREAM CONTEXT (Deep Debugging)")
|
|
311
|
+
parts.append(self.upstream_context.to_prompt_text())
|
|
312
|
+
|
|
313
|
+
return "\n".join(parts)
|