codemri 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.
- codemri/__init__.py +0 -0
- codemri/cli.py +365 -0
- codemri-0.1.0.dist-info/METADATA +89 -0
- codemri-0.1.0.dist-info/RECORD +7 -0
- codemri-0.1.0.dist-info/WHEEL +5 -0
- codemri-0.1.0.dist-info/entry_points.txt +2 -0
- codemri-0.1.0.dist-info/top_level.txt +1 -0
codemri/__init__.py
ADDED
|
File without changes
|
codemri/cli.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sqlite3
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="Instant code quality reports for your Python projects")
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
RADON_RANK_PENALTY = {
|
|
15
|
+
"A": 0,
|
|
16
|
+
"B": 5,
|
|
17
|
+
"C": 15,
|
|
18
|
+
"D": 30,
|
|
19
|
+
"E": 45,
|
|
20
|
+
"F": 60,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
GRADE_COLORS = {
|
|
24
|
+
"A": "#22c55e",
|
|
25
|
+
"B": "#84cc16",
|
|
26
|
+
"C": "#eab308",
|
|
27
|
+
"D": "#f97316",
|
|
28
|
+
"F": "#ef4444",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_ruff(path: str):
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
["ruff", "check", path, "--output-format=json"],
|
|
35
|
+
capture_output=True,
|
|
36
|
+
text=True,
|
|
37
|
+
)
|
|
38
|
+
if not result.stdout.strip():
|
|
39
|
+
return []
|
|
40
|
+
try:
|
|
41
|
+
return json.loads(result.stdout)
|
|
42
|
+
except json.JSONDecodeError:
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_radon_complexity(path: str):
|
|
47
|
+
result = subprocess.run(
|
|
48
|
+
["radon", "cc", path, "-j", "-e", "venv/*,*/venv/*,.venv/*,*/.venv/*"],
|
|
49
|
+
capture_output=True,
|
|
50
|
+
text=True,
|
|
51
|
+
)
|
|
52
|
+
if not result.stdout.strip():
|
|
53
|
+
return {}
|
|
54
|
+
try:
|
|
55
|
+
return json.loads(result.stdout)
|
|
56
|
+
except json.JSONDecodeError:
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def count_lines_of_code(path: str, complexity_data: dict) -> int:
|
|
61
|
+
total = 0
|
|
62
|
+
for file in complexity_data.keys():
|
|
63
|
+
try:
|
|
64
|
+
with open(file, "r", encoding="utf-8", errors="ignore") as f:
|
|
65
|
+
total += sum(1 for _ in f)
|
|
66
|
+
except (FileNotFoundError, OSError):
|
|
67
|
+
continue
|
|
68
|
+
return max(total, 1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def calculate_style_score(issues: list, loc: int) -> float:
|
|
72
|
+
issues_per_100_lines = (len(issues) / loc) * 100
|
|
73
|
+
score = 100 - (issues_per_100_lines * 4)
|
|
74
|
+
return max(0, min(100, score))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def calculate_complexity_score(complexity_data: dict) -> float:
|
|
78
|
+
all_blocks = [block for blocks in complexity_data.values() for block in blocks]
|
|
79
|
+
if not all_blocks:
|
|
80
|
+
return 100.0
|
|
81
|
+
total_penalty = sum(RADON_RANK_PENALTY.get(b.get("rank", "A"), 0) for b in all_blocks)
|
|
82
|
+
avg_penalty = total_penalty / len(all_blocks)
|
|
83
|
+
score = 100 - avg_penalty
|
|
84
|
+
return max(0, min(100, score))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def grade_and_color(score: float):
|
|
88
|
+
if score >= 90:
|
|
89
|
+
return "A", "bright_green"
|
|
90
|
+
elif score >= 75:
|
|
91
|
+
return "B", "green"
|
|
92
|
+
elif score >= 60:
|
|
93
|
+
return "C", "yellow"
|
|
94
|
+
elif score >= 40:
|
|
95
|
+
return "D", "orange3"
|
|
96
|
+
else:
|
|
97
|
+
return "F", "red"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def build_worst_offenders(complexity_data: dict, issues: list, limit: int = 5):
|
|
101
|
+
issues_per_file = {}
|
|
102
|
+
for issue in issues:
|
|
103
|
+
fname = issue.get("filename", "")
|
|
104
|
+
issues_per_file[fname] = issues_per_file.get(fname, 0) + 1
|
|
105
|
+
|
|
106
|
+
offenders = []
|
|
107
|
+
for file, blocks in complexity_data.items():
|
|
108
|
+
file_issue_count = issues_per_file.get(file, 0)
|
|
109
|
+
for block in blocks:
|
|
110
|
+
rank = block.get("rank", "A")
|
|
111
|
+
penalty = RADON_RANK_PENALTY.get(rank, 0)
|
|
112
|
+
severity = penalty + (file_issue_count * 2)
|
|
113
|
+
if severity > 0:
|
|
114
|
+
offenders.append({
|
|
115
|
+
"file": file,
|
|
116
|
+
"function": block.get("name", "?"),
|
|
117
|
+
"complexity": block.get("complexity", "?"),
|
|
118
|
+
"rank": rank,
|
|
119
|
+
"severity": severity,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
offenders.sort(key=lambda x: x["severity"], reverse=True)
|
|
123
|
+
return offenders[:limit]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_db_path(path: str) -> str:
|
|
127
|
+
base_dir = path if os.path.isdir(path) else os.path.dirname(path) or "."
|
|
128
|
+
return os.path.join(base_dir, ".codemri_history.db")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def save_run_to_history(path: str, overall_score: int, style_score: float, complexity_score: float):
|
|
132
|
+
db_path = get_db_path(path)
|
|
133
|
+
conn = sqlite3.connect(db_path)
|
|
134
|
+
cursor = conn.cursor()
|
|
135
|
+
cursor.execute("""
|
|
136
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
137
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
138
|
+
timestamp TEXT NOT NULL,
|
|
139
|
+
overall_score INTEGER NOT NULL,
|
|
140
|
+
style_score REAL NOT NULL,
|
|
141
|
+
complexity_score REAL NOT NULL
|
|
142
|
+
)
|
|
143
|
+
""")
|
|
144
|
+
cursor.execute(
|
|
145
|
+
"INSERT INTO runs (timestamp, overall_score, style_score, complexity_score) VALUES (?, ?, ?, ?)",
|
|
146
|
+
(datetime.now().isoformat(timespec="seconds"), overall_score, style_score, complexity_score),
|
|
147
|
+
)
|
|
148
|
+
conn.commit()
|
|
149
|
+
conn.close()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def load_history(path: str, limit: int = 10):
|
|
153
|
+
db_path = get_db_path(path)
|
|
154
|
+
if not os.path.exists(db_path):
|
|
155
|
+
return []
|
|
156
|
+
conn = sqlite3.connect(db_path)
|
|
157
|
+
cursor = conn.cursor()
|
|
158
|
+
cursor.execute(
|
|
159
|
+
"SELECT timestamp, overall_score, style_score, complexity_score "
|
|
160
|
+
"FROM runs ORDER BY id DESC LIMIT ?",
|
|
161
|
+
(limit,),
|
|
162
|
+
)
|
|
163
|
+
rows = cursor.fetchall()
|
|
164
|
+
conn.close()
|
|
165
|
+
return list(reversed(rows))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
HTML_TEMPLATE = """<!DOCTYPE html>
|
|
169
|
+
<html>
|
|
170
|
+
<head>
|
|
171
|
+
<meta charset="UTF-8">
|
|
172
|
+
<title>CodeMRI Code Quality Report</title>
|
|
173
|
+
<style>
|
|
174
|
+
body { font-family: -apple-system, Segoe UI, sans-serif; background: #0f172a; color: #e2e8f0; padding: 40px; }
|
|
175
|
+
h1 { color: #f8fafc; }
|
|
176
|
+
.score-panel { background: #1e293b; border-left: 6px solid __COLOR__; padding: 24px; border-radius: 8px; margin: 20px 0; }
|
|
177
|
+
.score { font-size: 48px; font-weight: bold; color: __COLOR__; }
|
|
178
|
+
.grade { font-size: 24px; color: __COLOR__; }
|
|
179
|
+
.subscore { display: inline-block; margin-right: 40px; margin-top: 10px; }
|
|
180
|
+
table { width: 100%; border-collapse: collapse; margin-top: 16px; background: #1e293b; border-radius: 8px; overflow: hidden; }
|
|
181
|
+
th { background: #334155; text-align: left; padding: 10px; color: #94a3b8; }
|
|
182
|
+
td { padding: 10px; border-top: 1px solid #334155; font-size: 14px; }
|
|
183
|
+
.badge { padding: 2px 10px; border-radius: 4px; color: white; font-weight: bold; }
|
|
184
|
+
.section-title { margin-top: 40px; color: #f8fafc; }
|
|
185
|
+
.meta { color: #64748b; font-size: 13px; }
|
|
186
|
+
</style>
|
|
187
|
+
</head>
|
|
188
|
+
<body>
|
|
189
|
+
<h1>CodeMRI Code Quality Report</h1>
|
|
190
|
+
<p class="meta">Path: __PATH__ | Generated: __TIMESTAMP__</p>
|
|
191
|
+
|
|
192
|
+
<div class="score-panel">
|
|
193
|
+
<div class="score">__SCORE__/100</div>
|
|
194
|
+
<div class="grade">Grade: __GRADE__</div>
|
|
195
|
+
<div class="subscore">Style: __STYLE_SCORE__/100</div>
|
|
196
|
+
<div class="subscore">Complexity: __COMPLEXITY_SCORE__/100</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<h2 class="section-title">Worst Offenders</h2>
|
|
200
|
+
<table>
|
|
201
|
+
<tr><th>File</th><th>Function</th><th>Complexity</th><th>Rank</th></tr>
|
|
202
|
+
__OFFENDERS_ROWS__
|
|
203
|
+
</table>
|
|
204
|
+
|
|
205
|
+
<h2 class="section-title">Style Issues</h2>
|
|
206
|
+
<table>
|
|
207
|
+
<tr><th>File</th><th>Line</th><th>Code</th><th>Message</th></tr>
|
|
208
|
+
__ISSUES_ROWS__
|
|
209
|
+
</table>
|
|
210
|
+
</body>
|
|
211
|
+
</html>"""
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def generate_html_report(path, overall_score, grade, style_score, complexity_score,
|
|
215
|
+
issues, complexity_data, worst_offenders, output_file="codemri_report.html"):
|
|
216
|
+
color = GRADE_COLORS.get(grade, "#6b7280")
|
|
217
|
+
|
|
218
|
+
offenders_rows = ""
|
|
219
|
+
for o in worst_offenders:
|
|
220
|
+
rank_color = GRADE_COLORS.get(o["rank"], "#6b7280")
|
|
221
|
+
offenders_rows += (
|
|
222
|
+
"<tr><td>" + str(o["file"]) + "</td><td>" + str(o["function"]) + "</td><td>"
|
|
223
|
+
+ str(o["complexity"]) + "</td><td><span class='badge' style='background:"
|
|
224
|
+
+ rank_color + "'>" + str(o["rank"]) + "</span></td></tr>"
|
|
225
|
+
)
|
|
226
|
+
if not offenders_rows:
|
|
227
|
+
offenders_rows = "<tr><td colspan='4'>No major offenders found</td></tr>"
|
|
228
|
+
|
|
229
|
+
issues_rows = ""
|
|
230
|
+
for i in issues[:20]:
|
|
231
|
+
filename = i.get("filename", "?")
|
|
232
|
+
line = i.get("location", {}).get("row", "?")
|
|
233
|
+
code = i.get("code", "?")
|
|
234
|
+
message = i.get("message", "?")
|
|
235
|
+
issues_rows += (
|
|
236
|
+
"<tr><td>" + str(filename) + "</td><td>" + str(line) + "</td><td>"
|
|
237
|
+
+ str(code) + "</td><td>" + str(message) + "</td></tr>"
|
|
238
|
+
)
|
|
239
|
+
if not issues_rows:
|
|
240
|
+
issues_rows = "<tr><td colspan='4'>No style issues found</td></tr>"
|
|
241
|
+
|
|
242
|
+
html = HTML_TEMPLATE
|
|
243
|
+
html = html.replace("__COLOR__", color)
|
|
244
|
+
html = html.replace("__PATH__", str(path))
|
|
245
|
+
html = html.replace("__TIMESTAMP__", datetime.now().strftime("%Y-%m-%d %H:%M"))
|
|
246
|
+
html = html.replace("__SCORE__", str(overall_score))
|
|
247
|
+
html = html.replace("__GRADE__", str(grade))
|
|
248
|
+
html = html.replace("__STYLE_SCORE__", str(round(style_score)))
|
|
249
|
+
html = html.replace("__COMPLEXITY_SCORE__", str(round(complexity_score)))
|
|
250
|
+
html = html.replace("__OFFENDERS_ROWS__", offenders_rows)
|
|
251
|
+
html = html.replace("__ISSUES_ROWS__", issues_rows)
|
|
252
|
+
|
|
253
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
254
|
+
f.write(html)
|
|
255
|
+
return output_file
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@app.command()
|
|
259
|
+
def scan(
|
|
260
|
+
path: str = typer.Argument(".", help="Path to the file or directory to analyze"),
|
|
261
|
+
html: bool = typer.Option(False, "--html", help="Generate a shareable HTML report"),
|
|
262
|
+
):
|
|
263
|
+
"""Analyze code quality for the given path."""
|
|
264
|
+
console.print(f"[bold cyan]Scanning:[/bold cyan] {path}\n")
|
|
265
|
+
|
|
266
|
+
issues = run_ruff(path)
|
|
267
|
+
complexity_data = run_radon_complexity(path)
|
|
268
|
+
loc = count_lines_of_code(path, complexity_data)
|
|
269
|
+
|
|
270
|
+
style_score = calculate_style_score(issues, loc)
|
|
271
|
+
complexity_score = calculate_complexity_score(complexity_data)
|
|
272
|
+
overall_score = round((style_score * 0.5) + (complexity_score * 0.5))
|
|
273
|
+
grade, color = grade_and_color(overall_score)
|
|
274
|
+
save_run_to_history(path, overall_score, style_score, complexity_score)
|
|
275
|
+
|
|
276
|
+
console.print(
|
|
277
|
+
Panel(
|
|
278
|
+
f"[bold {color}]{overall_score}/100[/bold {color}] Grade: [bold {color}]{grade}[/bold {color}]",
|
|
279
|
+
title="Overall Code Health",
|
|
280
|
+
border_style=color,
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
console.print(f"\n[bold]Style score:[/bold] {round(style_score)}/100 "
|
|
285
|
+
f"({len(issues)} issues across {loc} lines)")
|
|
286
|
+
console.print(f"[bold]Complexity score:[/bold] {round(complexity_score)}/100 "
|
|
287
|
+
f"({len(complexity_data)} files analyzed)\n")
|
|
288
|
+
|
|
289
|
+
worst_offenders = build_worst_offenders(complexity_data, issues)
|
|
290
|
+
if worst_offenders:
|
|
291
|
+
offenders_table = Table(show_header=True, header_style="bold red", title="Worst Offenders")
|
|
292
|
+
offenders_table.add_column("File")
|
|
293
|
+
offenders_table.add_column("Function")
|
|
294
|
+
offenders_table.add_column("Complexity")
|
|
295
|
+
offenders_table.add_column("Rank")
|
|
296
|
+
for o in worst_offenders:
|
|
297
|
+
offenders_table.add_row(o["file"], o["function"], str(o["complexity"]), o["rank"])
|
|
298
|
+
console.print(offenders_table)
|
|
299
|
+
console.print()
|
|
300
|
+
|
|
301
|
+
if issues:
|
|
302
|
+
table = Table(show_header=True, header_style="bold magenta", title="Style Issues")
|
|
303
|
+
table.add_column("File")
|
|
304
|
+
table.add_column("Line")
|
|
305
|
+
table.add_column("Code")
|
|
306
|
+
table.add_column("Message")
|
|
307
|
+
for issue in issues[:10]:
|
|
308
|
+
table.add_row(
|
|
309
|
+
issue.get("filename", "?"),
|
|
310
|
+
str(issue.get("location", {}).get("row", "?")),
|
|
311
|
+
issue.get("code", "?"),
|
|
312
|
+
issue.get("message", "?"),
|
|
313
|
+
)
|
|
314
|
+
console.print(table)
|
|
315
|
+
|
|
316
|
+
if html:
|
|
317
|
+
output_file = generate_html_report(
|
|
318
|
+
path, overall_score, grade, style_score, complexity_score,
|
|
319
|
+
issues, complexity_data, worst_offenders
|
|
320
|
+
)
|
|
321
|
+
full_path = os.path.abspath(output_file)
|
|
322
|
+
console.print(f"\n[bold green]HTML report generated:[/bold green] {full_path}")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@app.command()
|
|
326
|
+
def history(path: str = typer.Argument(".", help="Path whose scan history to show")):
|
|
327
|
+
"""Show score trend across previous scans."""
|
|
328
|
+
rows = load_history(path)
|
|
329
|
+
|
|
330
|
+
if not rows:
|
|
331
|
+
console.print("[yellow]No scan history found yet. Run 'lintra' a few times first.[/yellow]")
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
table = Table(show_header=True, header_style="bold cyan", title="Score History")
|
|
335
|
+
table.add_column("Timestamp")
|
|
336
|
+
table.add_column("Overall")
|
|
337
|
+
table.add_column("Style")
|
|
338
|
+
table.add_column("Complexity")
|
|
339
|
+
table.add_column("Trend")
|
|
340
|
+
|
|
341
|
+
prev_score = None
|
|
342
|
+
for timestamp, overall_score, style_score, complexity_score in rows:
|
|
343
|
+
if prev_score is None:
|
|
344
|
+
trend = "—"
|
|
345
|
+
elif overall_score > prev_score:
|
|
346
|
+
trend = "[green]▲ improving[/green]"
|
|
347
|
+
elif overall_score < prev_score:
|
|
348
|
+
trend = "[red]▼ declining[/red]"
|
|
349
|
+
else:
|
|
350
|
+
trend = "[dim]● stable[/dim]"
|
|
351
|
+
prev_score = overall_score
|
|
352
|
+
|
|
353
|
+
table.add_row(
|
|
354
|
+
timestamp,
|
|
355
|
+
f"{overall_score}/100",
|
|
356
|
+
f"{round(style_score)}/100",
|
|
357
|
+
f"{round(complexity_score)}/100",
|
|
358
|
+
trend,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
console.print(table)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
if __name__ == "__main__":
|
|
365
|
+
app()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codemri
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Instant code quality reports for your Python projects — style, complexity, and an overall health score in one command
|
|
5
|
+
Author-email: Suzzane Koranteng <korantengsuzzane@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: code quality,linting,complexity,cli,developer tools,static analysis
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: typer>=0.9.0
|
|
16
|
+
Requires-Dist: rich>=13.0.0
|
|
17
|
+
Requires-Dist: ruff>=0.4.0
|
|
18
|
+
Requires-Dist: radon>=6.0.0
|
|
19
|
+
|
|
20
|
+
# CodeMRI
|
|
21
|
+
|
|
22
|
+
**Instant code quality reports for your Python projects.**
|
|
23
|
+
|
|
24
|
+
CodeMRI scans your codebase and gives you a single, easy-to-read health score — combining style violations and code complexity into one number, so you know exactly how healthy your code is and where to focus first.
|
|
25
|
+
|
|
26
|
+
## Why CodeMRI?
|
|
27
|
+
|
|
28
|
+
Most linters dump a wall of warnings and leave you to figure out what matters. CodeMRI instead gives you:
|
|
29
|
+
|
|
30
|
+
- **One overall health score (0–100)** with a letter grade — like an MRI scan for your code
|
|
31
|
+
- **A "Worst Offenders" leaderboard** — the exact functions and files causing the most pain, ranked by severity
|
|
32
|
+
- **A clean, shareable HTML report** — perfect for pasting into a PR or sharing with your team
|
|
33
|
+
- **Score history tracking** — see whether your codebase is improving or rotting over time, right from the CLI
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install codemri
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
Scan the current directory:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
codemri scan .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Scan a specific file or folder:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
codemri scan path/to/project
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Generate a shareable HTML report:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
codemri scan . --html
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
View your score history over time:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
codemri history .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Check the installed version:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
codemri --version
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Example output
|
|
74
|
+
|
|
75
|
+
────────────────── Overall Code Health ──────────────────╮
|
|
76
|
+
│ 69/100 Grade: C │
|
|
77
|
+
╰────────────────────────────────────────────────────────╯
|
|
78
|
+
Style score: 39/100 (9 issues across 59 lines)
|
|
79
|
+
Complexity score: 98/100 (1 files analyzed)
|
|
80
|
+
|
|
81
|
+
## How scoring works
|
|
82
|
+
|
|
83
|
+
- **Style score** — based on the density of style/lint issues (via [ruff](https://github.com/astral-sh/ruff)) relative to lines of code
|
|
84
|
+
- **Complexity score** — based on cyclomatic complexity rankings (via [radon](https://github.com/rubik/radon)) across all functions
|
|
85
|
+
- **Overall score** — a 50/50 weighted average of the two, mapped to a letter grade (A–F)
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
codemri/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
codemri/cli.py,sha256=PsPwKUXl9GUWGFZfnr1YS77-fzFmwm9lY10tWpuaq0E,12770
|
|
3
|
+
codemri-0.1.0.dist-info/METADATA,sha256=26P-XqMKBEqTHSoAa8lvL7aRBgquBR85cAqSCPhF49A,2956
|
|
4
|
+
codemri-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
codemri-0.1.0.dist-info/entry_points.txt,sha256=hj9gBqNZnlrzGvdcYUrcL3z-ts3MoAsoCHf-k0zruQY,44
|
|
6
|
+
codemri-0.1.0.dist-info/top_level.txt,sha256=yxppBK1JJUobYTkWgNYyKHx6uT59-ZyklVOHI4Tdhwg,8
|
|
7
|
+
codemri-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codemri
|