socratic-analyzer 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.
- socratic_analyzer/__init__.py +42 -0
- socratic_analyzer/async_client.py +109 -0
- socratic_analyzer/client.py +475 -0
- socratic_analyzer/exceptions.py +55 -0
- socratic_analyzer/models.py +193 -0
- socratic_analyzer-0.1.0.dist-info/METADATA +414 -0
- socratic_analyzer-0.1.0.dist-info/RECORD +10 -0
- socratic_analyzer-0.1.0.dist-info/WHEEL +5 -0
- socratic_analyzer-0.1.0.dist-info/licenses/LICENSE +21 -0
- socratic_analyzer-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Socratic Analyzer - Production-grade code analysis package."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
__author__ = "Socratic Analyzer"
|
|
5
|
+
__email__ = "info@socratic-analyzer.dev"
|
|
6
|
+
|
|
7
|
+
from .async_client import AsyncAnalyzerClient
|
|
8
|
+
from .client import AnalyzerClient
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
AnalysisError,
|
|
11
|
+
AnalyzerError,
|
|
12
|
+
ConfigurationError,
|
|
13
|
+
LLMAnalysisError,
|
|
14
|
+
ParsingError,
|
|
15
|
+
PatternDetectionError,
|
|
16
|
+
ProviderNotFoundError,
|
|
17
|
+
ReportError,
|
|
18
|
+
ReportFormatError,
|
|
19
|
+
)
|
|
20
|
+
from .models import Analysis, AnalyzerConfig, CodeIssue, MetricResult, ProjectAnalysis
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Client
|
|
24
|
+
"AnalyzerClient",
|
|
25
|
+
"AsyncAnalyzerClient",
|
|
26
|
+
# Models
|
|
27
|
+
"Analysis",
|
|
28
|
+
"AnalyzerConfig",
|
|
29
|
+
"CodeIssue",
|
|
30
|
+
"MetricResult",
|
|
31
|
+
"ProjectAnalysis",
|
|
32
|
+
# Exceptions
|
|
33
|
+
"AnalyzerError",
|
|
34
|
+
"AnalysisError",
|
|
35
|
+
"ConfigurationError",
|
|
36
|
+
"ParsingError",
|
|
37
|
+
"PatternDetectionError",
|
|
38
|
+
"ReportError",
|
|
39
|
+
"ReportFormatError",
|
|
40
|
+
"ProviderNotFoundError",
|
|
41
|
+
"LLMAnalysisError",
|
|
42
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Async analyzer client interface."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from socratic_analyzer.client import AnalyzerClient
|
|
7
|
+
from socratic_analyzer.models import Analysis, AnalyzerConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncAnalyzerClient(AnalyzerClient):
|
|
11
|
+
"""Async analyzer client interface.
|
|
12
|
+
|
|
13
|
+
Provides async/await support for analyzing Python code.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
async def analyze_file_async(self, file_path: str) -> Analysis:
|
|
17
|
+
"""Analyze a single Python file asynchronously.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
file_path: Path to the Python file
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Analysis object with results
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
FileNotFoundError: If file doesn't exist
|
|
27
|
+
IOError: If file can't be read
|
|
28
|
+
"""
|
|
29
|
+
loop = asyncio.get_event_loop()
|
|
30
|
+
return await loop.run_in_executor(None, self.analyze_file, file_path)
|
|
31
|
+
|
|
32
|
+
async def analyze_code_async(
|
|
33
|
+
self, code: str, file_path: str = "unknown.py"
|
|
34
|
+
) -> Analysis:
|
|
35
|
+
"""Analyze Python code asynchronously.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
code: Python source code
|
|
39
|
+
file_path: Path to file (for reporting, optional)
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Analysis object with results
|
|
43
|
+
"""
|
|
44
|
+
loop = asyncio.get_event_loop()
|
|
45
|
+
return await loop.run_in_executor(
|
|
46
|
+
None, self.analyze_code, code, file_path
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def analyze_files_async(self, file_paths: List[str]) -> List[Analysis]:
|
|
50
|
+
"""Analyze multiple Python files asynchronously.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
file_paths: List of paths to Python files
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
List of Analysis objects
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
FileNotFoundError: If any file doesn't exist
|
|
60
|
+
IOError: If any file can't be read
|
|
61
|
+
"""
|
|
62
|
+
tasks = [self.analyze_file_async(fp) for fp in file_paths]
|
|
63
|
+
return await asyncio.gather(*tasks)
|
|
64
|
+
|
|
65
|
+
async def batch_analyze_code_async(
|
|
66
|
+
self, code_samples: List[tuple]
|
|
67
|
+
) -> List[Analysis]:
|
|
68
|
+
"""Analyze multiple code samples asynchronously.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
code_samples: List of (code, file_path) tuples
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List of Analysis objects
|
|
75
|
+
"""
|
|
76
|
+
tasks = [
|
|
77
|
+
self.analyze_code_async(code, fp or "unknown.py")
|
|
78
|
+
for code, fp in code_samples
|
|
79
|
+
]
|
|
80
|
+
return await asyncio.gather(*tasks)
|
|
81
|
+
|
|
82
|
+
async def generate_report_async(
|
|
83
|
+
self, analysis: Analysis, format: str = "text"
|
|
84
|
+
) -> str:
|
|
85
|
+
"""Generate formatted report asynchronously.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
analysis: Analysis object
|
|
89
|
+
format: Report format ("text", "json", "markdown")
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Formatted report string
|
|
93
|
+
"""
|
|
94
|
+
loop = asyncio.get_event_loop()
|
|
95
|
+
return await loop.run_in_executor(
|
|
96
|
+
None, self.generate_report, analysis, format
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def get_recommendations_async(self, analysis: Analysis) -> List[str]:
|
|
100
|
+
"""Get actionable recommendations asynchronously.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
analysis: Analysis object
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of recommendations
|
|
107
|
+
"""
|
|
108
|
+
loop = asyncio.get_event_loop()
|
|
109
|
+
return await loop.run_in_executor(None, self.get_recommendations, analysis)
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"""Main analyzer client interface."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from socratic_analyzer.analyzers.complexity import ComplexityAnalyzer
|
|
6
|
+
from socratic_analyzer.analyzers.imports import ImportAnalyzer
|
|
7
|
+
from socratic_analyzer.analyzers.metrics import MetricsAnalyzer
|
|
8
|
+
from socratic_analyzer.analyzers.static import StaticAnalyzer
|
|
9
|
+
from socratic_analyzer.analyzers.patterns import PatternAnalyzer
|
|
10
|
+
from socratic_analyzer.analyzers.smells import CodeSmellDetector
|
|
11
|
+
from socratic_analyzer.analyzers.performance import PerformanceAnalyzer
|
|
12
|
+
from socratic_analyzer.models import Analysis, AnalyzerConfig, CodeIssue, MetricResult
|
|
13
|
+
from socratic_analyzer.utils.quality_scorer import QualityScorer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AnalyzerClient:
|
|
17
|
+
"""Main analyzer client interface.
|
|
18
|
+
|
|
19
|
+
Provides a unified interface for analyzing Python code with multiple
|
|
20
|
+
specialized analyzers.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: Optional[AnalyzerConfig] = None) -> None:
|
|
24
|
+
"""Initialize analyzer client.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config: AnalyzerConfig object. If None, uses default configuration.
|
|
28
|
+
"""
|
|
29
|
+
self.config = config or AnalyzerConfig()
|
|
30
|
+
|
|
31
|
+
# Initialize analyzers
|
|
32
|
+
self._static_analyzer = StaticAnalyzer()
|
|
33
|
+
self._complexity_analyzer = ComplexityAnalyzer(
|
|
34
|
+
max_complexity=self.config.max_complexity
|
|
35
|
+
)
|
|
36
|
+
self._metrics_analyzer = MetricsAnalyzer()
|
|
37
|
+
self._import_analyzer = ImportAnalyzer()
|
|
38
|
+
self._pattern_analyzer = PatternAnalyzer()
|
|
39
|
+
self._smell_detector = CodeSmellDetector()
|
|
40
|
+
self._performance_analyzer = PerformanceAnalyzer()
|
|
41
|
+
|
|
42
|
+
def analyze_file(self, file_path: str) -> Analysis:
|
|
43
|
+
"""Analyze a single Python file.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
file_path: Path to the Python file
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Analysis object with results
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
FileNotFoundError: If file doesn't exist
|
|
53
|
+
IOError: If file can't be read
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
57
|
+
code = f.read()
|
|
58
|
+
except FileNotFoundError:
|
|
59
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
60
|
+
except IOError as e:
|
|
61
|
+
raise IOError(f"Error reading file {file_path}: {e}")
|
|
62
|
+
|
|
63
|
+
return self.analyze_code(code, file_path)
|
|
64
|
+
|
|
65
|
+
def analyze_code(self, code: str, file_path: str = "unknown.py") -> Analysis:
|
|
66
|
+
"""Analyze Python code.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
code: Python source code
|
|
70
|
+
file_path: Path to file (for reporting, optional)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Analysis object with results
|
|
74
|
+
"""
|
|
75
|
+
# Get file size
|
|
76
|
+
file_size = len(code.encode("utf-8"))
|
|
77
|
+
|
|
78
|
+
# Collect all issues
|
|
79
|
+
issues = []
|
|
80
|
+
|
|
81
|
+
# Run static analysis
|
|
82
|
+
if self.config.analyze_types or self.config.analyze_docstrings:
|
|
83
|
+
issues.extend(self._static_analyzer.analyze(code, file_path))
|
|
84
|
+
|
|
85
|
+
# Run complexity analysis
|
|
86
|
+
issues.extend(self._complexity_analyzer.analyze(code, file_path))
|
|
87
|
+
|
|
88
|
+
# Run import analysis
|
|
89
|
+
issues.extend(self._import_analyzer.analyze(code, file_path))
|
|
90
|
+
|
|
91
|
+
# Run pattern analysis
|
|
92
|
+
issues.extend(self._pattern_analyzer.analyze(code, file_path))
|
|
93
|
+
|
|
94
|
+
# Run code smell detection
|
|
95
|
+
issues.extend(self._smell_detector.analyze(code, file_path))
|
|
96
|
+
|
|
97
|
+
# Run performance analysis
|
|
98
|
+
issues.extend(self._performance_analyzer.analyze(code, file_path))
|
|
99
|
+
|
|
100
|
+
# Collect metrics
|
|
101
|
+
metrics = []
|
|
102
|
+
if self.config.include_metrics:
|
|
103
|
+
metrics.extend(self._metrics_analyzer.calculate_metrics(code))
|
|
104
|
+
|
|
105
|
+
# Detect patterns
|
|
106
|
+
patterns = self._detect_patterns(code, issues)
|
|
107
|
+
|
|
108
|
+
# Create analysis
|
|
109
|
+
analysis = Analysis(
|
|
110
|
+
file_path=file_path,
|
|
111
|
+
file_size=file_size,
|
|
112
|
+
language="python",
|
|
113
|
+
issues=issues,
|
|
114
|
+
metrics=metrics,
|
|
115
|
+
patterns=patterns,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return analysis
|
|
119
|
+
|
|
120
|
+
def _detect_patterns(self, code: str, issues: List[CodeIssue]) -> List[str]:
|
|
121
|
+
"""Detect code patterns from issues and code.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
code: Source code
|
|
125
|
+
issues: Detected issues
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List of detected patterns
|
|
129
|
+
"""
|
|
130
|
+
patterns = []
|
|
131
|
+
|
|
132
|
+
# Extract patterns from issue types
|
|
133
|
+
issue_types = set(i.issue_type for i in issues)
|
|
134
|
+
|
|
135
|
+
if "complexity" in issue_types:
|
|
136
|
+
patterns.append("High cyclomatic complexity detected")
|
|
137
|
+
|
|
138
|
+
if any("unused" in i.message.lower() for i in issues):
|
|
139
|
+
patterns.append("Unused variables detected")
|
|
140
|
+
|
|
141
|
+
if any("long" in i.message.lower() for i in issues):
|
|
142
|
+
patterns.append("Long lines detected")
|
|
143
|
+
|
|
144
|
+
if any("docstring" in i.message.lower() for i in issues):
|
|
145
|
+
patterns.append("Missing documentation detected")
|
|
146
|
+
|
|
147
|
+
if any("wildcard" in i.message.lower() for i in issues):
|
|
148
|
+
patterns.append("Wildcard imports detected")
|
|
149
|
+
|
|
150
|
+
if any("nesting" in i.message.lower() for i in issues):
|
|
151
|
+
patterns.append("Deep nesting detected")
|
|
152
|
+
|
|
153
|
+
# Check for specific code patterns
|
|
154
|
+
if "class " in code and "def __init__" in code:
|
|
155
|
+
patterns.append("Object-oriented design detected")
|
|
156
|
+
|
|
157
|
+
if "@property" in code:
|
|
158
|
+
patterns.append("Property decorators used")
|
|
159
|
+
|
|
160
|
+
if "async def" in code:
|
|
161
|
+
patterns.append("Async/await usage detected")
|
|
162
|
+
|
|
163
|
+
if "try:" in code and "except" in code:
|
|
164
|
+
patterns.append("Exception handling detected")
|
|
165
|
+
|
|
166
|
+
return patterns
|
|
167
|
+
|
|
168
|
+
def generate_report(
|
|
169
|
+
self, analysis: Analysis, format: str = "text"
|
|
170
|
+
) -> str:
|
|
171
|
+
"""Generate formatted report.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
analysis: Analysis object
|
|
175
|
+
format: Report format ("text", "json", "markdown")
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Formatted report string
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: If format is not supported
|
|
182
|
+
"""
|
|
183
|
+
if format == "json":
|
|
184
|
+
return self._format_json(analysis)
|
|
185
|
+
elif format == "markdown":
|
|
186
|
+
return self._format_markdown(analysis)
|
|
187
|
+
elif format == "text":
|
|
188
|
+
return self._format_text(analysis)
|
|
189
|
+
else:
|
|
190
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
191
|
+
|
|
192
|
+
def _format_text(self, analysis: Analysis) -> str:
|
|
193
|
+
"""Format analysis as text report.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
analysis: Analysis object
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Text report
|
|
200
|
+
"""
|
|
201
|
+
lines = []
|
|
202
|
+
lines.append("=" * 80)
|
|
203
|
+
lines.append(f"Analysis Report: {analysis.file_path}")
|
|
204
|
+
lines.append("=" * 80)
|
|
205
|
+
lines.append("")
|
|
206
|
+
|
|
207
|
+
# Summary
|
|
208
|
+
lines.append("SUMMARY")
|
|
209
|
+
lines.append(f" File size: {analysis.file_size:,} bytes")
|
|
210
|
+
lines.append(f" Language: {analysis.language}")
|
|
211
|
+
lines.append(f" Total issues: {analysis.total_issues}")
|
|
212
|
+
lines.append(f" - Critical: {analysis.critical_issues}")
|
|
213
|
+
lines.append(f" - High: {analysis.high_issues}")
|
|
214
|
+
lines.append("")
|
|
215
|
+
|
|
216
|
+
# Issues
|
|
217
|
+
if analysis.issues:
|
|
218
|
+
lines.append("ISSUES")
|
|
219
|
+
for issue in analysis.issues:
|
|
220
|
+
severity_label = issue.severity.upper()
|
|
221
|
+
lines.append(f" {severity_label}: {issue.location}")
|
|
222
|
+
lines.append(f" {issue.message}")
|
|
223
|
+
if issue.suggestion:
|
|
224
|
+
lines.append(f" Suggestion: {issue.suggestion}")
|
|
225
|
+
lines.append("")
|
|
226
|
+
else:
|
|
227
|
+
lines.append("No issues found!")
|
|
228
|
+
lines.append("")
|
|
229
|
+
|
|
230
|
+
# Metrics
|
|
231
|
+
if analysis.metrics:
|
|
232
|
+
lines.append("METRICS")
|
|
233
|
+
for metric in analysis.metrics:
|
|
234
|
+
status_indicator = "[OK]" if metric.status == "ok" else "[!]"
|
|
235
|
+
lines.append(
|
|
236
|
+
f" {status_indicator} {metric.name}: {metric.value:.1f}"
|
|
237
|
+
)
|
|
238
|
+
if metric.description:
|
|
239
|
+
lines.append(f" {metric.description}")
|
|
240
|
+
lines.append("")
|
|
241
|
+
|
|
242
|
+
# Patterns
|
|
243
|
+
if analysis.patterns:
|
|
244
|
+
lines.append("PATTERNS DETECTED")
|
|
245
|
+
for pattern in analysis.patterns:
|
|
246
|
+
lines.append(f" - {pattern}")
|
|
247
|
+
lines.append("")
|
|
248
|
+
|
|
249
|
+
lines.append("=" * 80)
|
|
250
|
+
return "\n".join(lines)
|
|
251
|
+
|
|
252
|
+
def _format_json(self, analysis: Analysis) -> str:
|
|
253
|
+
"""Format analysis as JSON report.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
analysis: Analysis object
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
JSON report
|
|
260
|
+
"""
|
|
261
|
+
import json
|
|
262
|
+
from datetime import datetime
|
|
263
|
+
|
|
264
|
+
data = {
|
|
265
|
+
"file_path": analysis.file_path,
|
|
266
|
+
"file_size": analysis.file_size,
|
|
267
|
+
"language": analysis.language,
|
|
268
|
+
"timestamp": analysis.timestamp.isoformat(),
|
|
269
|
+
"summary": {
|
|
270
|
+
"total_issues": analysis.total_issues,
|
|
271
|
+
"critical_issues": analysis.critical_issues,
|
|
272
|
+
"high_issues": analysis.high_issues,
|
|
273
|
+
},
|
|
274
|
+
"issues": [
|
|
275
|
+
{
|
|
276
|
+
"type": issue.issue_type,
|
|
277
|
+
"severity": issue.severity,
|
|
278
|
+
"location": issue.location,
|
|
279
|
+
"message": issue.message,
|
|
280
|
+
"suggestion": issue.suggestion,
|
|
281
|
+
}
|
|
282
|
+
for issue in analysis.issues
|
|
283
|
+
],
|
|
284
|
+
"metrics": [
|
|
285
|
+
{
|
|
286
|
+
"name": metric.name,
|
|
287
|
+
"value": metric.value,
|
|
288
|
+
"threshold": metric.threshold,
|
|
289
|
+
"status": metric.status,
|
|
290
|
+
"description": metric.description,
|
|
291
|
+
}
|
|
292
|
+
for metric in analysis.metrics
|
|
293
|
+
],
|
|
294
|
+
"patterns": analysis.patterns,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return json.dumps(data, indent=2)
|
|
298
|
+
|
|
299
|
+
def _format_markdown(self, analysis: Analysis) -> str:
|
|
300
|
+
"""Format analysis as Markdown report.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
analysis: Analysis object
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Markdown report
|
|
307
|
+
"""
|
|
308
|
+
lines = []
|
|
309
|
+
lines.append(f"# Analysis Report: {analysis.file_path}")
|
|
310
|
+
lines.append("")
|
|
311
|
+
|
|
312
|
+
# Summary
|
|
313
|
+
lines.append("## Summary")
|
|
314
|
+
lines.append(
|
|
315
|
+
f"- **File size**: {analysis.file_size:,} bytes"
|
|
316
|
+
)
|
|
317
|
+
lines.append(f"- **Language**: {analysis.language}")
|
|
318
|
+
lines.append(f"- **Issues**: {analysis.total_issues}")
|
|
319
|
+
lines.append(f" - Critical: {analysis.critical_issues}")
|
|
320
|
+
lines.append(f" - High: {analysis.high_issues}")
|
|
321
|
+
lines.append("")
|
|
322
|
+
|
|
323
|
+
# Issues by severity
|
|
324
|
+
if analysis.issues:
|
|
325
|
+
lines.append("## Issues")
|
|
326
|
+
|
|
327
|
+
critical = [i for i in analysis.issues if i.severity == "critical"]
|
|
328
|
+
if critical:
|
|
329
|
+
lines.append("### Critical")
|
|
330
|
+
for issue in critical:
|
|
331
|
+
lines.append(f"- **{issue.location}**: {issue.message}")
|
|
332
|
+
if issue.suggestion:
|
|
333
|
+
lines.append(f" - Suggestion: {issue.suggestion}")
|
|
334
|
+
lines.append("")
|
|
335
|
+
|
|
336
|
+
high = [i for i in analysis.issues if i.severity == "high"]
|
|
337
|
+
if high:
|
|
338
|
+
lines.append("### High")
|
|
339
|
+
for issue in high:
|
|
340
|
+
lines.append(f"- **{issue.location}**: {issue.message}")
|
|
341
|
+
lines.append("")
|
|
342
|
+
|
|
343
|
+
medium = [i for i in analysis.issues if i.severity == "medium"]
|
|
344
|
+
if medium:
|
|
345
|
+
lines.append("### Medium")
|
|
346
|
+
for issue in medium:
|
|
347
|
+
lines.append(f"- {issue.location}: {issue.message}")
|
|
348
|
+
lines.append("")
|
|
349
|
+
|
|
350
|
+
low = [i for i in analysis.issues if i.severity in ("low", "info")]
|
|
351
|
+
if low:
|
|
352
|
+
lines.append("### Low/Info")
|
|
353
|
+
for issue in low:
|
|
354
|
+
lines.append(f"- {issue.location}: {issue.message}")
|
|
355
|
+
lines.append("")
|
|
356
|
+
else:
|
|
357
|
+
lines.append("## Issues")
|
|
358
|
+
lines.append("No issues found! ✅")
|
|
359
|
+
lines.append("")
|
|
360
|
+
|
|
361
|
+
# Metrics
|
|
362
|
+
if analysis.metrics:
|
|
363
|
+
lines.append("## Metrics")
|
|
364
|
+
for metric in analysis.metrics:
|
|
365
|
+
lines.append(
|
|
366
|
+
f"- **{metric.name}**: {metric.value:.1f} "
|
|
367
|
+
f"(status: {metric.status})"
|
|
368
|
+
)
|
|
369
|
+
lines.append("")
|
|
370
|
+
|
|
371
|
+
# Patterns
|
|
372
|
+
if analysis.patterns:
|
|
373
|
+
lines.append("## Patterns Detected")
|
|
374
|
+
for pattern in analysis.patterns:
|
|
375
|
+
lines.append(f"- {pattern}")
|
|
376
|
+
lines.append("")
|
|
377
|
+
|
|
378
|
+
return "\n".join(lines)
|
|
379
|
+
|
|
380
|
+
def get_recommendations(self, analysis: Analysis) -> List[str]:
|
|
381
|
+
"""Get actionable recommendations from analysis.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
analysis: Analysis object
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
List of recommendations
|
|
388
|
+
"""
|
|
389
|
+
recommendations = []
|
|
390
|
+
|
|
391
|
+
# Add quality score and rating
|
|
392
|
+
quality_report = QualityScorer.create_quality_report(analysis)
|
|
393
|
+
score = quality_report["overall_score"]
|
|
394
|
+
rating = quality_report["rating"]
|
|
395
|
+
recommendations.append(f"[SCORE] Quality Score: {score}/100 ({rating})")
|
|
396
|
+
|
|
397
|
+
# Critical issues
|
|
398
|
+
critical_count = analysis.critical_issues
|
|
399
|
+
if critical_count > 0:
|
|
400
|
+
recommendations.append(
|
|
401
|
+
f"[CRITICAL] Fix {critical_count} critical issue(s) immediately"
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# High severity issues
|
|
405
|
+
high_count = analysis.high_issues
|
|
406
|
+
if high_count > 0:
|
|
407
|
+
recommendations.append(
|
|
408
|
+
f"[HIGH] Address {high_count} high-severity issue(s)"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# Complexity issues
|
|
412
|
+
complexity_issues = [i for i in analysis.issues if i.issue_type == "complexity"]
|
|
413
|
+
if complexity_issues:
|
|
414
|
+
recommendations.append(
|
|
415
|
+
"[COMPLEXITY] Refactor complex functions (consider breaking into smaller functions)"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Performance issues
|
|
419
|
+
performance_issues = [i for i in analysis.issues if i.issue_type == "performance"]
|
|
420
|
+
if performance_issues:
|
|
421
|
+
recommendations.append(
|
|
422
|
+
f"[PERFORMANCE] Optimize {len(performance_issues)} performance issue(s)"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Code smell issues
|
|
426
|
+
smell_issues = [i for i in analysis.issues if i.issue_type == "smell"]
|
|
427
|
+
if smell_issues:
|
|
428
|
+
recommendations.append(
|
|
429
|
+
f"[SMELL] Address {len(smell_issues)} code smell(s) for maintainability"
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Pattern-related recommendations
|
|
433
|
+
pattern_issues = [i for i in analysis.issues if i.issue_type == "pattern"]
|
|
434
|
+
if pattern_issues:
|
|
435
|
+
recommendations.append(
|
|
436
|
+
f"[PATTERNS] Review {len(pattern_issues)} design pattern(s)"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Documentation issues
|
|
440
|
+
doc_issues = [
|
|
441
|
+
i for i in analysis.issues if "docstring" in i.message.lower()
|
|
442
|
+
]
|
|
443
|
+
if doc_issues:
|
|
444
|
+
recommendations.append(
|
|
445
|
+
f"[DOCS] Add documentation to {len(doc_issues)} item(s)"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# Style issues
|
|
449
|
+
style_issues = [i for i in analysis.issues if i.issue_type == "style"]
|
|
450
|
+
if len(style_issues) > 5:
|
|
451
|
+
recommendations.append(
|
|
452
|
+
"[STYLE] Address multiple style issues (consider using a code formatter)"
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Maintainability
|
|
456
|
+
if analysis.metrics:
|
|
457
|
+
maintainability = next(
|
|
458
|
+
(m for m in analysis.metrics if m.name == "maintainability_index"),
|
|
459
|
+
None,
|
|
460
|
+
)
|
|
461
|
+
if maintainability and maintainability.value < 50:
|
|
462
|
+
recommendations.append(
|
|
463
|
+
"[MAINTAINABILITY] Maintainability is low - consider refactoring"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Add quality scorer suggestions if score is low
|
|
467
|
+
if score < 70:
|
|
468
|
+
for suggestion in quality_report.get("suggestions", []):
|
|
469
|
+
if not any(rec.startswith("[") for rec in [suggestion]):
|
|
470
|
+
recommendations.append(f"[IMPROVEMENT] {suggestion}")
|
|
471
|
+
|
|
472
|
+
if len(recommendations) == 1: # Only has quality score
|
|
473
|
+
recommendations.append("[OK] Code quality is acceptable")
|
|
474
|
+
|
|
475
|
+
return recommendations
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Custom exceptions for Socratic Analyzer."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AnalyzerError(Exception):
|
|
5
|
+
"""Base exception for analyzer operations."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AnalysisError(AnalyzerError):
|
|
11
|
+
"""Error during code analysis."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigurationError(AnalyzerError):
|
|
17
|
+
"""Error in analyzer configuration."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ParsingError(AnalysisError):
|
|
23
|
+
"""Error parsing code."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PatternDetectionError(AnalysisError):
|
|
29
|
+
"""Error detecting patterns."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ReportError(AnalyzerError):
|
|
35
|
+
"""Error generating report."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ReportFormatError(ReportError):
|
|
41
|
+
"""Unsupported report format."""
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ProviderNotFoundError(AnalyzerError):
|
|
47
|
+
"""Provider not found."""
|
|
48
|
+
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LLMAnalysisError(AnalyzerError):
|
|
53
|
+
"""Error in LLM-powered analysis."""
|
|
54
|
+
|
|
55
|
+
pass
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Data models for Socratic Analyzer."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class CodeIssue:
|
|
10
|
+
"""Detected code issue."""
|
|
11
|
+
|
|
12
|
+
issue_type: str # "complexity", "style", "security", "performance", "maintenance"
|
|
13
|
+
severity: str # "critical", "high", "medium", "low", "info"
|
|
14
|
+
location: str # "file.py:123" or "module.function:456"
|
|
15
|
+
message: str
|
|
16
|
+
suggestion: Optional[str] = None
|
|
17
|
+
code_snippet: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
def __repr__(self) -> str:
|
|
20
|
+
"""Return string representation."""
|
|
21
|
+
return f"{self.severity.upper()}: {self.location} - {self.message}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class MetricResult:
|
|
26
|
+
"""Code quality metric result."""
|
|
27
|
+
|
|
28
|
+
name: str # "cyclomatic_complexity", "maintainability_index", etc.
|
|
29
|
+
value: float
|
|
30
|
+
threshold: Optional[float] = None
|
|
31
|
+
status: str = "info" # "ok", "warning", "critical"
|
|
32
|
+
description: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
"""Return string representation."""
|
|
36
|
+
return f"{self.name}: {self.value} (status: {self.status})"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Analysis:
|
|
41
|
+
"""Complete code analysis for a single file."""
|
|
42
|
+
|
|
43
|
+
file_path: str
|
|
44
|
+
file_size: int
|
|
45
|
+
language: str
|
|
46
|
+
issues: List[CodeIssue] = field(default_factory=list)
|
|
47
|
+
metrics: List[MetricResult] = field(default_factory=list)
|
|
48
|
+
patterns: List[str] = field(default_factory=list)
|
|
49
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def critical_issues(self) -> int:
|
|
53
|
+
"""Count of critical issues."""
|
|
54
|
+
return len([i for i in self.issues if i.severity == "critical"])
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def high_issues(self) -> int:
|
|
58
|
+
"""Count of high severity issues."""
|
|
59
|
+
return len([i for i in self.issues if i.severity == "high"])
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def total_issues(self) -> int:
|
|
63
|
+
"""Total number of issues."""
|
|
64
|
+
return len(self.issues)
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
"""Return string representation."""
|
|
68
|
+
return f"Analysis({self.file_path}, issues={self.total_issues}, patterns={len(self.patterns)})"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class ProjectAnalysis:
|
|
73
|
+
"""Project-wide analysis aggregating multiple file analyses."""
|
|
74
|
+
|
|
75
|
+
project_path: str
|
|
76
|
+
files_analyzed: int
|
|
77
|
+
analyses: List[Analysis] = field(default_factory=list)
|
|
78
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def total_issues(self) -> int:
|
|
82
|
+
"""Total issues across all files."""
|
|
83
|
+
return sum(a.total_issues for a in self.analyses)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def critical_issues(self) -> int:
|
|
87
|
+
"""Total critical issues across all files."""
|
|
88
|
+
return sum(a.critical_issues for a in self.analyses)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def high_issues(self) -> int:
|
|
92
|
+
"""Total high severity issues across all files."""
|
|
93
|
+
return sum(a.high_issues for a in self.analyses)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def overall_score(self) -> float:
|
|
97
|
+
"""Overall quality score (0-100)."""
|
|
98
|
+
if not self.analyses:
|
|
99
|
+
return 100.0
|
|
100
|
+
|
|
101
|
+
# Calculate score based on issues
|
|
102
|
+
total_issues = self.total_issues
|
|
103
|
+
critical_weight = 10
|
|
104
|
+
high_weight = 5
|
|
105
|
+
medium_weight = 2
|
|
106
|
+
low_weight = 1
|
|
107
|
+
|
|
108
|
+
weighted_issues = 0
|
|
109
|
+
for analysis in self.analyses:
|
|
110
|
+
for issue in analysis.issues:
|
|
111
|
+
if issue.severity == "critical":
|
|
112
|
+
weighted_issues += critical_weight
|
|
113
|
+
elif issue.severity == "high":
|
|
114
|
+
weighted_issues += high_weight
|
|
115
|
+
elif issue.severity == "medium":
|
|
116
|
+
weighted_issues += medium_weight
|
|
117
|
+
elif issue.severity == "low":
|
|
118
|
+
weighted_issues += low_weight
|
|
119
|
+
|
|
120
|
+
# Scale: 100 - (weighted_issues / total_issues_possible) * 100
|
|
121
|
+
# Cap the score between 0 and 100
|
|
122
|
+
score = max(0.0, 100.0 - (weighted_issues / max(1, total_issues * 5)))
|
|
123
|
+
return min(100.0, score)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def recommendations(self) -> List[str]:
|
|
127
|
+
"""Get recommendations based on analysis."""
|
|
128
|
+
recommendations = []
|
|
129
|
+
|
|
130
|
+
if self.critical_issues > 0:
|
|
131
|
+
recommendations.append(f"Fix {self.critical_issues} critical issue(s) immediately")
|
|
132
|
+
|
|
133
|
+
if self.high_issues > 5:
|
|
134
|
+
recommendations.append(f"Address {self.high_issues} high-severity issues")
|
|
135
|
+
|
|
136
|
+
avg_issues = self.total_issues / max(1, self.files_analyzed)
|
|
137
|
+
if avg_issues > 5:
|
|
138
|
+
recommendations.append("High issue density - consider refactoring affected files")
|
|
139
|
+
|
|
140
|
+
return recommendations
|
|
141
|
+
|
|
142
|
+
def __repr__(self) -> str:
|
|
143
|
+
"""Return string representation."""
|
|
144
|
+
return (
|
|
145
|
+
f"ProjectAnalysis({self.project_path}, "
|
|
146
|
+
f"files={self.files_analyzed}, "
|
|
147
|
+
f"issues={self.total_issues}, "
|
|
148
|
+
f"score={self.overall_score:.1f})"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class AnalyzerConfig:
|
|
154
|
+
"""Configuration for Socratic Analyzer."""
|
|
155
|
+
|
|
156
|
+
# Analysis options
|
|
157
|
+
analyze_types: bool = True # Check type hints
|
|
158
|
+
analyze_docstrings: bool = True # Check documentation
|
|
159
|
+
analyze_security: bool = True # Check security issues
|
|
160
|
+
analyze_performance: bool = True # Check performance antipatterns
|
|
161
|
+
analyze_patterns: bool = True # Detect design patterns
|
|
162
|
+
|
|
163
|
+
# Thresholds
|
|
164
|
+
max_complexity: int = 10 # Max cyclomatic complexity
|
|
165
|
+
max_line_length: int = 120 # Max line length
|
|
166
|
+
min_docstring_length: int = 10 # Min docstring length
|
|
167
|
+
|
|
168
|
+
# Output
|
|
169
|
+
include_metrics: bool = True
|
|
170
|
+
include_patterns: bool = True
|
|
171
|
+
detailed_output: bool = False
|
|
172
|
+
|
|
173
|
+
# Optional LLM analysis
|
|
174
|
+
use_llm: bool = False
|
|
175
|
+
llm_provider: str = "anthropic"
|
|
176
|
+
llm_model: str = "claude-opus"
|
|
177
|
+
|
|
178
|
+
def __post_init__(self) -> None:
|
|
179
|
+
"""Validate configuration."""
|
|
180
|
+
if self.max_complexity <= 0:
|
|
181
|
+
raise ValueError("max_complexity must be positive")
|
|
182
|
+
if self.max_line_length <= 0:
|
|
183
|
+
raise ValueError("max_line_length must be positive")
|
|
184
|
+
if self.min_docstring_length < 0:
|
|
185
|
+
raise ValueError("min_docstring_length must be non-negative")
|
|
186
|
+
|
|
187
|
+
def __repr__(self) -> str:
|
|
188
|
+
"""Return string representation."""
|
|
189
|
+
return (
|
|
190
|
+
f"AnalyzerConfig(complexity={self.max_complexity}, "
|
|
191
|
+
f"line_length={self.max_line_length}, "
|
|
192
|
+
f"llm={self.use_llm})"
|
|
193
|
+
)
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: socratic-analyzer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-grade code analysis package with LLM-powered insights
|
|
5
|
+
Author-email: Socratic Analyzer <info@socratic-analyzer.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Nireus79/Socratic-analyzer
|
|
8
|
+
Project-URL: Documentation, https://github.com/Nireus79/Socratic-analyzer/tree/main/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/Nireus79/Socratic-analyzer.git
|
|
10
|
+
Project-URL: Issues, https://github.com/Nireus79/Socratic-analyzer/issues
|
|
11
|
+
Keywords: code-analysis,static-analysis,metrics,linting,quality,insights
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: socrates-nexus>=0.1.0
|
|
26
|
+
Provides-Extra: openclaw
|
|
27
|
+
Provides-Extra: langchain
|
|
28
|
+
Requires-Dist: langchain>=0.1.0; extra == "langchain"
|
|
29
|
+
Provides-Extra: llm
|
|
30
|
+
Requires-Dist: socrates-nexus>=0.1.0; extra == "llm"
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: langchain>=0.1.0; extra == "all"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
38
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
41
|
+
Requires-Dist: langchain>=0.1.0; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# Socratic Analyzer
|
|
45
|
+
|
|
46
|
+
Production-grade code analysis package with LLM-powered insights. Analyze Python code for issues, metrics, patterns, and get intelligent recommendations.
|
|
47
|
+
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
[](https://pypi.org/)
|
|
51
|
+
|
|
52
|
+
## Overview
|
|
53
|
+
|
|
54
|
+
Socratic Analyzer provides automated code analysis with static analysis, complexity metrics, pattern detection, and LLM-powered recommendations. Perfect for code quality assurance, refactoring guidance, and architectural insights.
|
|
55
|
+
|
|
56
|
+
### Key Features
|
|
57
|
+
|
|
58
|
+
- **Static Code Analysis** - Detect issues, violations, and code smells
|
|
59
|
+
- **Complexity Metrics** - Calculate cyclomatic complexity, maintainability index, and more
|
|
60
|
+
- **Pattern Detection** - Identify antipatterns, design patterns, and performance issues
|
|
61
|
+
- **Security Analysis** - Find potential security vulnerabilities
|
|
62
|
+
- **Documentation Analysis** - Check docstrings and type hints
|
|
63
|
+
- **Project-Wide Analysis** - Aggregate metrics across entire projects
|
|
64
|
+
- **Quality Scoring** - Get overall code quality score (0-100)
|
|
65
|
+
- **LLM Integration** - Get intelligent recommendations via Socrates Nexus
|
|
66
|
+
- **Multiple Formats** - Generate reports in text, JSON, and Markdown
|
|
67
|
+
- **Framework Integration** - Use as Openclaw skill or LangChain tool
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Basic installation
|
|
73
|
+
pip install socratic-analyzer
|
|
74
|
+
|
|
75
|
+
# With Openclaw support
|
|
76
|
+
pip install socratic-analyzer[openclaw]
|
|
77
|
+
|
|
78
|
+
# With LangChain support
|
|
79
|
+
pip install socratic-analyzer[langchain]
|
|
80
|
+
|
|
81
|
+
# With all features
|
|
82
|
+
pip install socratic-analyzer[all]
|
|
83
|
+
|
|
84
|
+
# Development
|
|
85
|
+
pip install socratic-analyzer[dev]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
### Basic File Analysis
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from socratic_analyzer import AnalyzerClient, AnalyzerConfig
|
|
94
|
+
|
|
95
|
+
# Create analyzer
|
|
96
|
+
analyzer = AnalyzerClient(AnalyzerConfig())
|
|
97
|
+
|
|
98
|
+
# Analyze a file
|
|
99
|
+
analysis = analyzer.analyze_file("mycode.py")
|
|
100
|
+
|
|
101
|
+
# Print results
|
|
102
|
+
print(f"Issues: {analysis.total_issues}")
|
|
103
|
+
print(f"Patterns detected: {len(analysis.patterns)}")
|
|
104
|
+
|
|
105
|
+
# Generate report
|
|
106
|
+
report = analyzer.generate_report(analysis, format="text")
|
|
107
|
+
print(report)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Project Analysis
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from socratic_analyzer import AnalyzerClient
|
|
114
|
+
|
|
115
|
+
analyzer = AnalyzerClient()
|
|
116
|
+
|
|
117
|
+
# Analyze entire project
|
|
118
|
+
project_analysis = analyzer.analyze_project("./src")
|
|
119
|
+
|
|
120
|
+
print(f"Files analyzed: {project_analysis.files_analyzed}")
|
|
121
|
+
print(f"Total issues: {project_analysis.total_issues}")
|
|
122
|
+
print(f"Quality score: {project_analysis.overall_score:.1f}/100")
|
|
123
|
+
|
|
124
|
+
# Get recommendations
|
|
125
|
+
for recommendation in project_analysis.recommendations:
|
|
126
|
+
print(f"- {recommendation}")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### LLM-Powered Analysis
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from socratic_analyzer import AnalyzerClient
|
|
133
|
+
from socratic_analyzer.llm import LLMPoweredAnalyzer
|
|
134
|
+
from socrates_nexus import LLMClient
|
|
135
|
+
|
|
136
|
+
# Create components
|
|
137
|
+
analyzer = AnalyzerClient()
|
|
138
|
+
llm_client = LLMClient(provider="anthropic", model="claude-opus")
|
|
139
|
+
|
|
140
|
+
# Create LLM-powered analyzer
|
|
141
|
+
llm_analyzer = LLMPoweredAnalyzer(analyzer, llm_client)
|
|
142
|
+
|
|
143
|
+
# Get analysis with intelligent insights
|
|
144
|
+
result = llm_analyzer.analyze_with_insights("complex_module.py")
|
|
145
|
+
print(result["llm_insights"])
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Openclaw Integration
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from socratic_analyzer.integrations.openclaw import AnalyzerSkill
|
|
152
|
+
|
|
153
|
+
# Use in Openclaw
|
|
154
|
+
skill = AnalyzerSkill(detailed=True)
|
|
155
|
+
result = skill.analyze("mycode.py")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### LangChain Integration
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from socratic_analyzer.integrations.langchain import SocraticAnalyzerTool
|
|
162
|
+
from langchain.agents import AgentType, initialize_agent, Tool
|
|
163
|
+
from langchain.llms import OpenAI
|
|
164
|
+
|
|
165
|
+
# Create tool
|
|
166
|
+
analyzer_tool = SocraticAnalyzerTool()
|
|
167
|
+
|
|
168
|
+
# Use in LangChain agent
|
|
169
|
+
tools = [
|
|
170
|
+
Tool(
|
|
171
|
+
name="analyze_code",
|
|
172
|
+
func=analyzer_tool._run,
|
|
173
|
+
description="Analyze Python code for issues and patterns"
|
|
174
|
+
)
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
agent = initialize_agent(
|
|
178
|
+
tools,
|
|
179
|
+
OpenAI(temperature=0),
|
|
180
|
+
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
|
181
|
+
verbose=True
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Architecture
|
|
186
|
+
|
|
187
|
+
### Core Components
|
|
188
|
+
|
|
189
|
+
**Analyzers** - Pluggable analysis strategies:
|
|
190
|
+
- StaticAnalyzer - Code issues and violations
|
|
191
|
+
- ComplexityAnalyzer - Cyclomatic complexity
|
|
192
|
+
- MetricsAnalyzer - Code metrics
|
|
193
|
+
- DocstringAnalyzer - Documentation quality
|
|
194
|
+
- TypeHintAnalyzer - Type hint completeness
|
|
195
|
+
- SecurityAnalyzer - Security issues
|
|
196
|
+
|
|
197
|
+
**Patterns** - Pattern detection:
|
|
198
|
+
- AntipatternDetector - Common antipatterns
|
|
199
|
+
- DesignPatternDetector - Design patterns
|
|
200
|
+
- PerformancePatternDetector - Performance issues
|
|
201
|
+
|
|
202
|
+
**Reports** - Output formats:
|
|
203
|
+
- TextReportFormatter
|
|
204
|
+
- JSONReportFormatter
|
|
205
|
+
- MarkdownReportFormatter
|
|
206
|
+
|
|
207
|
+
**Insights** - Intelligence generation:
|
|
208
|
+
- Scoring system (0-100 quality score)
|
|
209
|
+
- Recommendation generator
|
|
210
|
+
- LLM integration for intelligent insights
|
|
211
|
+
|
|
212
|
+
## Configuration
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from socratic_analyzer import AnalyzerConfig, AnalyzerClient
|
|
216
|
+
|
|
217
|
+
config = AnalyzerConfig(
|
|
218
|
+
# Analysis options
|
|
219
|
+
analyze_types=True, # Check type hints
|
|
220
|
+
analyze_docstrings=True, # Check documentation
|
|
221
|
+
analyze_security=True, # Check security issues
|
|
222
|
+
analyze_performance=True, # Check performance antipatterns
|
|
223
|
+
|
|
224
|
+
# Thresholds
|
|
225
|
+
max_complexity=10, # Max cyclomatic complexity
|
|
226
|
+
max_line_length=120, # Max line length
|
|
227
|
+
min_docstring_length=10, # Min docstring length
|
|
228
|
+
|
|
229
|
+
# Output
|
|
230
|
+
include_metrics=True,
|
|
231
|
+
include_patterns=True,
|
|
232
|
+
detailed_output=False,
|
|
233
|
+
|
|
234
|
+
# LLM Integration
|
|
235
|
+
use_llm=True,
|
|
236
|
+
llm_provider="anthropic",
|
|
237
|
+
llm_model="claude-opus",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
analyzer = AnalyzerClient(config)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Output Formats
|
|
244
|
+
|
|
245
|
+
### Text Report
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
=== Analysis Report: example.py ===
|
|
249
|
+
|
|
250
|
+
FILE INFORMATION
|
|
251
|
+
Size: 2,048 bytes
|
|
252
|
+
Lines: 45
|
|
253
|
+
|
|
254
|
+
ISSUES (5 total)
|
|
255
|
+
CRITICAL: example.py:12 - Function 'process_data' is too complex (CC: 15)
|
|
256
|
+
Suggestion: Refactor into smaller functions
|
|
257
|
+
|
|
258
|
+
HIGH: example.py:25 - Unused variable 'temp_result'
|
|
259
|
+
|
|
260
|
+
MEDIUM: example.py:8 - Missing type hints
|
|
261
|
+
|
|
262
|
+
METRICS
|
|
263
|
+
Cyclomatic Complexity: 15 (threshold: 10)
|
|
264
|
+
Maintainability Index: 62.5
|
|
265
|
+
|
|
266
|
+
PATTERNS DETECTED
|
|
267
|
+
- Unused variable pattern
|
|
268
|
+
- Long function pattern
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### JSON Report
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"file_path": "example.py",
|
|
276
|
+
"total_issues": 5,
|
|
277
|
+
"critical_issues": 1,
|
|
278
|
+
"issues": [
|
|
279
|
+
{
|
|
280
|
+
"type": "complexity",
|
|
281
|
+
"severity": "critical",
|
|
282
|
+
"location": "example.py:12",
|
|
283
|
+
"message": "Function too complex",
|
|
284
|
+
"suggestion": "Refactor..."
|
|
285
|
+
}
|
|
286
|
+
],
|
|
287
|
+
"metrics": [...],
|
|
288
|
+
"overall_score": 62.5
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Markdown Report
|
|
293
|
+
|
|
294
|
+
```markdown
|
|
295
|
+
# Analysis Report: example.py
|
|
296
|
+
|
|
297
|
+
## Summary
|
|
298
|
+
- **Files analyzed**: 1
|
|
299
|
+
- **Issues**: 5 (1 critical, 1 high, 3 medium)
|
|
300
|
+
- **Quality score**: 62.5/100
|
|
301
|
+
|
|
302
|
+
## Critical Issues
|
|
303
|
+
1. **Function 'process_data' too complex** (CC: 15)
|
|
304
|
+
- Location: `example.py:12`
|
|
305
|
+
- Suggestion: Refactor into smaller functions
|
|
306
|
+
|
|
307
|
+
## Recommendations
|
|
308
|
+
- Fix critical issues immediately
|
|
309
|
+
- Address high-severity issues
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Examples
|
|
313
|
+
|
|
314
|
+
See the `examples/` directory for complete working examples:
|
|
315
|
+
|
|
316
|
+
- `01_basic_analysis.py` - Analyze a single file
|
|
317
|
+
- `02_pattern_detection.py` - Detect code patterns
|
|
318
|
+
- `03_scoring_analysis.py` - Get quality scores
|
|
319
|
+
- `04_project_analysis.py` - Analyze entire projects
|
|
320
|
+
- `05_openclaw_integration.py` - Use as Openclaw skill
|
|
321
|
+
- `06_langchain_integration.py` - Use in LangChain agents
|
|
322
|
+
- `07_llm_powered_analysis.py` - Get LLM insights
|
|
323
|
+
|
|
324
|
+
## Testing
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Run all tests
|
|
328
|
+
pytest
|
|
329
|
+
|
|
330
|
+
# Run with coverage
|
|
331
|
+
pytest --cov=src/socratic_analyzer
|
|
332
|
+
|
|
333
|
+
# Run specific test
|
|
334
|
+
pytest tests/test_models.py::TestAnalyzerConfig
|
|
335
|
+
|
|
336
|
+
# Run benchmarks
|
|
337
|
+
pytest tests/benchmarks/ -v
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## API Reference
|
|
341
|
+
|
|
342
|
+
### AnalyzerClient
|
|
343
|
+
|
|
344
|
+
Main interface for code analysis.
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
class AnalyzerClient:
|
|
348
|
+
def analyze_file(self, file_path: str) -> Analysis
|
|
349
|
+
def analyze_project(self, project_path: str) -> ProjectAnalysis
|
|
350
|
+
def generate_report(self, analysis, format: str) -> str
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Analysis Model
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
@dataclass
|
|
357
|
+
class Analysis:
|
|
358
|
+
file_path: str
|
|
359
|
+
file_size: int
|
|
360
|
+
language: str
|
|
361
|
+
issues: List[CodeIssue]
|
|
362
|
+
metrics: List[MetricResult]
|
|
363
|
+
patterns: List[str]
|
|
364
|
+
timestamp: datetime
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### CodeIssue Model
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
@dataclass
|
|
371
|
+
class CodeIssue:
|
|
372
|
+
issue_type: str # "complexity", "style", "security", etc.
|
|
373
|
+
severity: str # "critical", "high", "medium", "low", "info"
|
|
374
|
+
location: str # "file.py:123"
|
|
375
|
+
message: str
|
|
376
|
+
suggestion: Optional[str]
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Performance
|
|
380
|
+
|
|
381
|
+
Based on benchmarks:
|
|
382
|
+
|
|
383
|
+
- **Single file** (1-5 KB): ~10-50ms
|
|
384
|
+
- **Medium file** (50 KB): ~100-200ms
|
|
385
|
+
- **Large file** (500 KB): ~500ms-1s
|
|
386
|
+
- **Batch operation**: 10-15ms per file
|
|
387
|
+
|
|
388
|
+
## Integration with Socrates Ecosystem
|
|
389
|
+
|
|
390
|
+
Socratic Analyzer integrates seamlessly with:
|
|
391
|
+
|
|
392
|
+
- **Socrates Nexus** - Multi-provider LLM client for intelligent recommendations
|
|
393
|
+
- **Openclaw** - Available as reusable skill
|
|
394
|
+
- **LangChain** - Available as tool for agents
|
|
395
|
+
|
|
396
|
+
## Contributing
|
|
397
|
+
|
|
398
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
399
|
+
|
|
400
|
+
## License
|
|
401
|
+
|
|
402
|
+
MIT License - See [LICENSE](LICENSE) for details.
|
|
403
|
+
|
|
404
|
+
## Support
|
|
405
|
+
|
|
406
|
+
- GitHub Issues: https://github.com/Nireus79/Socratic-analyzer/issues
|
|
407
|
+
- Documentation: https://github.com/Nireus79/Socratic-analyzer/tree/main/docs
|
|
408
|
+
- Email: info@socratic-analyzer.dev
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
**Built with ❤️ as part of the Socrates ecosystem**
|
|
413
|
+
|
|
414
|
+
Made by [@Nireus79](https://github.com/Nireus79)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
socratic_analyzer/__init__.py,sha256=35c0Sto5Fjoebe8bfu84BCuHeFHodsgAbN6F2s6IpL8,975
|
|
2
|
+
socratic_analyzer/async_client.py,sha256=Yb4K5r9Grgvhv6H00PlgwHi-eo_WNT4ZKyCTKy7Rdgo,3195
|
|
3
|
+
socratic_analyzer/client.py,sha256=0gcP7Wdevu8kdGZCdcx3UMVshvGQd9dJmPYnY4zrKgw,16188
|
|
4
|
+
socratic_analyzer/exceptions.py,sha256=jaYAV2wnEW7pLL3zCoYh4ucTRi6G4LXkq0tiEpnCTbE,838
|
|
5
|
+
socratic_analyzer/models.py,sha256=DsioZ_kEG8iV_5mfad0WjZStQaNTSCh2joqyExw6bcg,6399
|
|
6
|
+
socratic_analyzer-0.1.0.dist-info/licenses/LICENSE,sha256=p8Gke7rq86rZFh4asQGnpwd8eE9Uryt736OXxnNsFWM,1087
|
|
7
|
+
socratic_analyzer-0.1.0.dist-info/METADATA,sha256=oNd9ILl2tQloA9WKrjoDMdw7ViiQ557BW7q7Yndd0M0,11543
|
|
8
|
+
socratic_analyzer-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
+
socratic_analyzer-0.1.0.dist-info/top_level.txt,sha256=nafJktQlDC-B50uKGvJXGoXkIBKBk5eOyPCbTCqg7AY,18
|
|
10
|
+
socratic_analyzer-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Socratic Analyzer Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
socratic_analyzer
|