codebase-digest-ai 0.1.1__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.
- codebase_digest/__init__.py +8 -0
- codebase_digest/analyzer/__init__.py +7 -0
- codebase_digest/analyzer/codebase_analyzer.py +183 -0
- codebase_digest/analyzer/flow_analyzer.py +164 -0
- codebase_digest/analyzer/metrics_analyzer.py +130 -0
- codebase_digest/cli/__init__.py +1 -0
- codebase_digest/cli/main.py +284 -0
- codebase_digest/exporters/__init__.py +9 -0
- codebase_digest/exporters/graph_exporter.py +1038 -0
- codebase_digest/exporters/html_exporter.py +1052 -0
- codebase_digest/exporters/json_exporter.py +105 -0
- codebase_digest/exporters/markdown_exporter.py +273 -0
- codebase_digest/exporters/readme_exporter.py +306 -0
- codebase_digest/models.py +81 -0
- codebase_digest/parser/__init__.py +7 -0
- codebase_digest/parser/base.py +41 -0
- codebase_digest/parser/javascript_parser.py +36 -0
- codebase_digest/parser/python_parser.py +270 -0
- codebase_digest_ai-0.1.1.dist-info/METADATA +233 -0
- codebase_digest_ai-0.1.1.dist-info/RECORD +24 -0
- codebase_digest_ai-0.1.1.dist-info/WHEEL +5 -0
- codebase_digest_ai-0.1.1.dist-info/entry_points.txt +2 -0
- codebase_digest_ai-0.1.1.dist-info/licenses/LICENSE +21 -0
- codebase_digest_ai-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
"""HTML report exporter."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
from ..models import CodebaseAnalysis
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HTMLExporter:
|
|
11
|
+
"""Exports analysis results to HTML format."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, analysis: CodebaseAnalysis):
|
|
14
|
+
self.analysis = analysis
|
|
15
|
+
|
|
16
|
+
def export(self, output_path: Path) -> None:
|
|
17
|
+
"""Export analysis to HTML file."""
|
|
18
|
+
html_content = self._generate_html()
|
|
19
|
+
output_path.write_text(html_content, encoding='utf-8')
|
|
20
|
+
|
|
21
|
+
def _generate_html(self) -> str:
|
|
22
|
+
"""Generate complete HTML report."""
|
|
23
|
+
return f"""<!DOCTYPE html>
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<head>
|
|
26
|
+
<meta charset="UTF-8">
|
|
27
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
28
|
+
<title>Codebase Analysis - {self.analysis.root_path.name}</title>
|
|
29
|
+
<style>
|
|
30
|
+
{self._get_css()}
|
|
31
|
+
</style>
|
|
32
|
+
</head>
|
|
33
|
+
<body>
|
|
34
|
+
<div class="container">
|
|
35
|
+
{self._generate_header()}
|
|
36
|
+
{self._generate_project_summary()}
|
|
37
|
+
{self._generate_summary_metrics()}
|
|
38
|
+
{self._generate_architecture()}
|
|
39
|
+
{self._generate_directory_structure()}
|
|
40
|
+
{self._generate_key_components()}
|
|
41
|
+
{self._generate_core_logic()}
|
|
42
|
+
{self._generate_dependencies()}
|
|
43
|
+
{self._generate_data_flow()}
|
|
44
|
+
{self._generate_risks()}
|
|
45
|
+
{self._generate_recommendations()}
|
|
46
|
+
</div>
|
|
47
|
+
</body>
|
|
48
|
+
</html>"""
|
|
49
|
+
|
|
50
|
+
def _get_css(self) -> str:
|
|
51
|
+
"""Generate CSS styles."""
|
|
52
|
+
return """
|
|
53
|
+
:root {
|
|
54
|
+
--accent: #3b82f6;
|
|
55
|
+
--bg: #f8fafc;
|
|
56
|
+
--surface: #ffffff;
|
|
57
|
+
--soft: #f1f5f9;
|
|
58
|
+
--text: #0f172a;
|
|
59
|
+
--muted: #64748b;
|
|
60
|
+
--border: #e2e8f0;
|
|
61
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
62
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
63
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
64
|
+
--radius: 8px;
|
|
65
|
+
--spacing-xs: 8px;
|
|
66
|
+
--spacing-sm: 12px;
|
|
67
|
+
--spacing-md: 16px;
|
|
68
|
+
--spacing-lg: 24px;
|
|
69
|
+
--spacing-xl: 32px;
|
|
70
|
+
--spacing-2xl: 48px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
* {
|
|
74
|
+
margin: 0;
|
|
75
|
+
padding: 0;
|
|
76
|
+
box-sizing: border-box;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
body {
|
|
80
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
line-height: 1.5;
|
|
83
|
+
color: var(--text);
|
|
84
|
+
background: var(--bg);
|
|
85
|
+
-webkit-font-smoothing: antialiased;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.container {
|
|
89
|
+
max-width: 1200px;
|
|
90
|
+
margin: 0 auto;
|
|
91
|
+
padding: var(--spacing-lg);
|
|
92
|
+
padding-top: 120px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header {
|
|
96
|
+
position: fixed;
|
|
97
|
+
top: 0;
|
|
98
|
+
left: 0;
|
|
99
|
+
right: 0;
|
|
100
|
+
background: rgba(255, 255, 255, 0.95);
|
|
101
|
+
backdrop-filter: blur(8px);
|
|
102
|
+
border-bottom: 1px solid var(--border);
|
|
103
|
+
z-index: 100;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.header-content {
|
|
107
|
+
max-width: 1200px;
|
|
108
|
+
margin: 0 auto;
|
|
109
|
+
padding: var(--spacing-lg);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.header h1 {
|
|
113
|
+
font-size: 32px;
|
|
114
|
+
font-weight: 700;
|
|
115
|
+
color: var(--text);
|
|
116
|
+
margin-bottom: var(--spacing-sm);
|
|
117
|
+
letter-spacing: -0.025em;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.meta-row {
|
|
121
|
+
display: flex;
|
|
122
|
+
gap: var(--spacing-xl);
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
color: var(--muted);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.meta-item {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: var(--spacing-xs);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.meta-label {
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
text-transform: uppercase;
|
|
136
|
+
letter-spacing: 0.05em;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.project-summary {
|
|
140
|
+
background: linear-gradient(135deg, var(--surface) 0%, var(--soft) 100%);
|
|
141
|
+
padding: var(--spacing-2xl);
|
|
142
|
+
border-radius: var(--radius);
|
|
143
|
+
border: 1px solid var(--border);
|
|
144
|
+
border-left: 4px solid var(--accent);
|
|
145
|
+
margin-bottom: var(--spacing-2xl);
|
|
146
|
+
box-shadow: var(--shadow-sm);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.project-summary h2 {
|
|
150
|
+
font-size: 18px;
|
|
151
|
+
font-weight: 700;
|
|
152
|
+
color: var(--text);
|
|
153
|
+
margin-bottom: var(--spacing-md);
|
|
154
|
+
letter-spacing: -0.01em;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.project-summary p {
|
|
158
|
+
color: var(--muted);
|
|
159
|
+
line-height: 1.6;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.metrics-grid {
|
|
163
|
+
display: grid;
|
|
164
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
165
|
+
gap: var(--spacing-lg);
|
|
166
|
+
margin-bottom: var(--spacing-2xl);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.metric-card {
|
|
170
|
+
background: var(--surface);
|
|
171
|
+
padding: var(--spacing-xl);
|
|
172
|
+
border-radius: var(--radius);
|
|
173
|
+
border: 1px solid var(--border);
|
|
174
|
+
border-top: 3px solid var(--accent);
|
|
175
|
+
box-shadow: var(--shadow-sm);
|
|
176
|
+
transition: all 150ms ease;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.metric-card:hover {
|
|
180
|
+
transform: translateY(-2px);
|
|
181
|
+
box-shadow: var(--shadow-md);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.metric-value {
|
|
185
|
+
font-size: 36px;
|
|
186
|
+
font-weight: 800;
|
|
187
|
+
color: var(--text);
|
|
188
|
+
margin-bottom: var(--spacing-xs);
|
|
189
|
+
line-height: 1;
|
|
190
|
+
letter-spacing: -0.02em;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.metric-title {
|
|
194
|
+
font-size: 14px;
|
|
195
|
+
font-weight: 700;
|
|
196
|
+
color: var(--text);
|
|
197
|
+
margin-bottom: 4px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.metric-caption {
|
|
201
|
+
font-size: 12px;
|
|
202
|
+
color: var(--muted);
|
|
203
|
+
line-height: 1.4;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.section {
|
|
207
|
+
background: var(--surface);
|
|
208
|
+
padding: var(--spacing-2xl);
|
|
209
|
+
border-radius: var(--radius);
|
|
210
|
+
border: 1px solid var(--border);
|
|
211
|
+
border-left: 4px solid var(--accent);
|
|
212
|
+
margin-bottom: var(--spacing-xl);
|
|
213
|
+
box-shadow: var(--shadow-sm);
|
|
214
|
+
transition: box-shadow 150ms ease;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.section:nth-child(even) {
|
|
218
|
+
background: var(--soft);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.section:hover {
|
|
222
|
+
box-shadow: var(--shadow-md);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.section h2 {
|
|
226
|
+
font-size: 18px;
|
|
227
|
+
font-weight: 700;
|
|
228
|
+
color: var(--text);
|
|
229
|
+
margin-bottom: var(--spacing-xl);
|
|
230
|
+
padding-bottom: var(--spacing-md);
|
|
231
|
+
border-bottom: 1px solid var(--border);
|
|
232
|
+
letter-spacing: -0.01em;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.section h3 {
|
|
236
|
+
font-size: 14px;
|
|
237
|
+
font-weight: 700;
|
|
238
|
+
color: var(--text);
|
|
239
|
+
margin: var(--spacing-xl) 0 var(--spacing-md) 0;
|
|
240
|
+
text-transform: uppercase;
|
|
241
|
+
letter-spacing: 0.05em;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.directory-tree {
|
|
245
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
246
|
+
font-size: 13px;
|
|
247
|
+
background: #1e293b;
|
|
248
|
+
color: #e2e8f0;
|
|
249
|
+
padding: var(--spacing-lg);
|
|
250
|
+
border-radius: var(--radius);
|
|
251
|
+
line-height: 1.6;
|
|
252
|
+
white-space: pre;
|
|
253
|
+
overflow-x: auto;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.component-tables {
|
|
257
|
+
display: grid;
|
|
258
|
+
gap: var(--spacing-2xl);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.table {
|
|
262
|
+
width: 100%;
|
|
263
|
+
border-collapse: collapse;
|
|
264
|
+
border-radius: var(--radius);
|
|
265
|
+
overflow: hidden;
|
|
266
|
+
border: 1px solid var(--border);
|
|
267
|
+
box-shadow: var(--shadow-sm);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.table th,
|
|
271
|
+
.table td {
|
|
272
|
+
padding: var(--spacing-md) var(--spacing-lg);
|
|
273
|
+
text-align: left;
|
|
274
|
+
border-bottom: 1px solid var(--border);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.table th {
|
|
278
|
+
background: var(--soft);
|
|
279
|
+
font-weight: 700;
|
|
280
|
+
color: var(--text);
|
|
281
|
+
font-size: 12px;
|
|
282
|
+
text-transform: uppercase;
|
|
283
|
+
letter-spacing: 0.05em;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.table td {
|
|
287
|
+
color: var(--muted);
|
|
288
|
+
font-size: 14px;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.table tbody tr {
|
|
292
|
+
transition: background-color 150ms ease;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.table tbody tr:hover {
|
|
296
|
+
background: var(--soft);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.table tbody tr:last-child td {
|
|
300
|
+
border-bottom: none;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.flow-timeline {
|
|
304
|
+
position: relative;
|
|
305
|
+
padding-left: var(--spacing-xl);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.flow-timeline::before {
|
|
309
|
+
content: '';
|
|
310
|
+
position: absolute;
|
|
311
|
+
left: 12px;
|
|
312
|
+
top: 0;
|
|
313
|
+
bottom: 0;
|
|
314
|
+
width: 2px;
|
|
315
|
+
background: var(--accent);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.flow-card {
|
|
319
|
+
position: relative;
|
|
320
|
+
background: var(--surface);
|
|
321
|
+
padding: var(--spacing-lg);
|
|
322
|
+
border-radius: var(--radius);
|
|
323
|
+
border: 1px solid var(--border);
|
|
324
|
+
margin-bottom: var(--spacing-lg);
|
|
325
|
+
box-shadow: var(--shadow-sm);
|
|
326
|
+
margin-left: var(--spacing-lg);
|
|
327
|
+
transition: all 150ms ease;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.flow-card::before {
|
|
331
|
+
content: '';
|
|
332
|
+
position: absolute;
|
|
333
|
+
left: -31px;
|
|
334
|
+
top: 20px;
|
|
335
|
+
width: 12px;
|
|
336
|
+
height: 12px;
|
|
337
|
+
border-radius: 50%;
|
|
338
|
+
background: var(--accent);
|
|
339
|
+
border: 3px solid var(--surface);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.flow-card:hover {
|
|
343
|
+
transform: translateX(4px);
|
|
344
|
+
box-shadow: var(--shadow-md);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.flow-card:last-child {
|
|
348
|
+
margin-bottom: 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.flow-name {
|
|
352
|
+
font-size: 16px;
|
|
353
|
+
font-weight: 700;
|
|
354
|
+
color: var(--text);
|
|
355
|
+
margin-bottom: var(--spacing-xs);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.flow-description {
|
|
359
|
+
font-size: 14px;
|
|
360
|
+
color: var(--muted);
|
|
361
|
+
margin-bottom: var(--spacing-md);
|
|
362
|
+
line-height: 1.5;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.flow-steps {
|
|
366
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
367
|
+
font-size: 12px;
|
|
368
|
+
color: var(--text);
|
|
369
|
+
background: var(--soft);
|
|
370
|
+
padding: var(--spacing-md);
|
|
371
|
+
border-radius: var(--radius);
|
|
372
|
+
border: 1px solid var(--border);
|
|
373
|
+
line-height: 1.4;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.dependency-item {
|
|
377
|
+
display: flex;
|
|
378
|
+
align-items: center;
|
|
379
|
+
justify-content: space-between;
|
|
380
|
+
padding: var(--spacing-md) 0;
|
|
381
|
+
border-bottom: 1px solid var(--border);
|
|
382
|
+
transition: all 150ms ease;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.dependency-item:hover {
|
|
386
|
+
background: var(--soft);
|
|
387
|
+
margin: 0 calc(-1 * var(--spacing-xl));
|
|
388
|
+
padding-left: var(--spacing-xl);
|
|
389
|
+
padding-right: var(--spacing-xl);
|
|
390
|
+
border-radius: var(--radius);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.dependency-item:last-child {
|
|
394
|
+
border-bottom: none;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.dependency-name {
|
|
398
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
399
|
+
font-size: 14px;
|
|
400
|
+
color: var(--text);
|
|
401
|
+
font-weight: 600;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.dependency-bar {
|
|
405
|
+
display: flex;
|
|
406
|
+
align-items: center;
|
|
407
|
+
gap: var(--spacing-md);
|
|
408
|
+
min-width: 140px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.dependency-count {
|
|
412
|
+
font-size: 12px;
|
|
413
|
+
color: var(--muted);
|
|
414
|
+
min-width: 24px;
|
|
415
|
+
font-weight: 600;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.dependency-rail {
|
|
419
|
+
flex: 1;
|
|
420
|
+
height: 6px;
|
|
421
|
+
background: var(--soft);
|
|
422
|
+
border-radius: 3px;
|
|
423
|
+
overflow: hidden;
|
|
424
|
+
border: 1px solid var(--border);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.bar {
|
|
428
|
+
height: 100%;
|
|
429
|
+
background: var(--accent);
|
|
430
|
+
border-radius: 2px;
|
|
431
|
+
min-width: 2px;
|
|
432
|
+
transition: width 300ms ease;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.risk-item {
|
|
436
|
+
background: var(--surface);
|
|
437
|
+
border: 1px solid var(--border);
|
|
438
|
+
border-radius: var(--radius);
|
|
439
|
+
padding: var(--spacing-lg);
|
|
440
|
+
margin-bottom: var(--spacing-md);
|
|
441
|
+
box-shadow: var(--shadow-sm);
|
|
442
|
+
transition: all 150ms ease;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.risk-item:hover {
|
|
446
|
+
box-shadow: var(--shadow-md);
|
|
447
|
+
transform: translateY(-1px);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.risk-item.low {
|
|
451
|
+
border-left: 4px solid var(--accent);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.risk-item.medium {
|
|
455
|
+
border-left: 4px solid #f59e0b;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.risk-item.high {
|
|
459
|
+
border-left: 4px solid #ef4444;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.risk-header {
|
|
463
|
+
display: flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
gap: var(--spacing-md);
|
|
466
|
+
margin-bottom: var(--spacing-sm);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.risk-severity {
|
|
470
|
+
font-size: 10px;
|
|
471
|
+
font-weight: 800;
|
|
472
|
+
padding: 4px 8px;
|
|
473
|
+
border-radius: 4px;
|
|
474
|
+
text-transform: uppercase;
|
|
475
|
+
letter-spacing: 0.05em;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.risk-severity.low {
|
|
479
|
+
background: var(--accent);
|
|
480
|
+
color: white;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.risk-severity.medium {
|
|
484
|
+
background: #f59e0b;
|
|
485
|
+
color: white;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.risk-severity.high {
|
|
489
|
+
background: #ef4444;
|
|
490
|
+
color: white;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.risk-reason {
|
|
494
|
+
font-size: 14px;
|
|
495
|
+
font-weight: 700;
|
|
496
|
+
color: var(--text);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.risk-evidence {
|
|
500
|
+
font-size: 12px;
|
|
501
|
+
color: var(--muted);
|
|
502
|
+
margin-top: 4px;
|
|
503
|
+
line-height: 1.4;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.recommendation-item {
|
|
507
|
+
background: var(--surface);
|
|
508
|
+
border: 1px solid var(--border);
|
|
509
|
+
border-radius: var(--radius);
|
|
510
|
+
padding: var(--spacing-lg);
|
|
511
|
+
margin-bottom: var(--spacing-md);
|
|
512
|
+
box-shadow: var(--shadow-sm);
|
|
513
|
+
transition: all 150ms ease;
|
|
514
|
+
border-left: 4px solid var(--accent);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.recommendation-item:hover {
|
|
518
|
+
box-shadow: var(--shadow-md);
|
|
519
|
+
transform: translateY(-1px);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.recommendation-header {
|
|
523
|
+
display: flex;
|
|
524
|
+
align-items: center;
|
|
525
|
+
gap: var(--spacing-md);
|
|
526
|
+
margin-bottom: var(--spacing-sm);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.recommendation-priority {
|
|
530
|
+
font-size: 10px;
|
|
531
|
+
font-weight: 800;
|
|
532
|
+
padding: 4px 8px;
|
|
533
|
+
border-radius: 4px;
|
|
534
|
+
text-transform: uppercase;
|
|
535
|
+
letter-spacing: 0.05em;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.recommendation-priority.high {
|
|
539
|
+
background: #ef4444;
|
|
540
|
+
color: white;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.recommendation-priority.medium {
|
|
544
|
+
background: #f59e0b;
|
|
545
|
+
color: white;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.recommendation-priority.low {
|
|
549
|
+
background: var(--accent);
|
|
550
|
+
color: white;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.recommendation-text {
|
|
554
|
+
font-size: 14px;
|
|
555
|
+
font-weight: 700;
|
|
556
|
+
color: var(--text);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.recommendation-rationale {
|
|
560
|
+
font-size: 12px;
|
|
561
|
+
color: var(--muted);
|
|
562
|
+
margin-top: 4px;
|
|
563
|
+
line-height: 1.4;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.recommendation-actions {
|
|
567
|
+
margin-top: var(--spacing-md);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.recommendation-link {
|
|
571
|
+
display: inline-flex;
|
|
572
|
+
align-items: center;
|
|
573
|
+
gap: var(--spacing-xs);
|
|
574
|
+
color: var(--accent);
|
|
575
|
+
text-decoration: none;
|
|
576
|
+
font-size: 12px;
|
|
577
|
+
font-weight: 700;
|
|
578
|
+
text-transform: uppercase;
|
|
579
|
+
letter-spacing: 0.05em;
|
|
580
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
581
|
+
border-radius: 4px;
|
|
582
|
+
transition: all 150ms ease;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.recommendation-link:hover {
|
|
586
|
+
background: var(--soft);
|
|
587
|
+
color: var(--text);
|
|
588
|
+
transform: translateX(2px);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.badge {
|
|
592
|
+
display: inline-block;
|
|
593
|
+
padding: 4px 8px;
|
|
594
|
+
background: var(--soft);
|
|
595
|
+
color: var(--muted);
|
|
596
|
+
border-radius: 4px;
|
|
597
|
+
font-size: 11px;
|
|
598
|
+
font-weight: 600;
|
|
599
|
+
border: 1px solid var(--border);
|
|
600
|
+
}
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
def _generate_header(self) -> str:
|
|
604
|
+
"""Generate header section."""
|
|
605
|
+
primary_language = list(self.analysis.languages)[0] if self.analysis.languages else 'Unknown'
|
|
606
|
+
|
|
607
|
+
return f"""
|
|
608
|
+
<div class="header">
|
|
609
|
+
<div class="header-content">
|
|
610
|
+
<h1>{self.analysis.root_path.name}</h1>
|
|
611
|
+
<div class="meta-row">
|
|
612
|
+
<div class="meta-item">
|
|
613
|
+
<span class="meta-label">Primary Language:</span>
|
|
614
|
+
<span>{primary_language}</span>
|
|
615
|
+
</div>
|
|
616
|
+
<div class="meta-item">
|
|
617
|
+
<span class="meta-label">Total LOC:</span>
|
|
618
|
+
<span>{self.analysis.total_lines:,}</span>
|
|
619
|
+
</div>
|
|
620
|
+
<div class="meta-item">
|
|
621
|
+
<span class="meta-label">Generated:</span>
|
|
622
|
+
<span>{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</span>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
"""
|
|
628
|
+
|
|
629
|
+
def _generate_project_summary(self) -> str:
|
|
630
|
+
"""Generate project summary section."""
|
|
631
|
+
# Infer project type and domain from analysis
|
|
632
|
+
domain_entities = [entity.name.lower() for entity in self.analysis.domain_entities]
|
|
633
|
+
|
|
634
|
+
# Determine domain
|
|
635
|
+
domain = "Unknown"
|
|
636
|
+
if any(term in domain_entities for term in ['user', 'account', 'auth']):
|
|
637
|
+
if any(term in domain_entities for term in ['payment', 'wallet', 'transaction']):
|
|
638
|
+
domain = "Financial/Payment System"
|
|
639
|
+
else:
|
|
640
|
+
domain = "User Management System"
|
|
641
|
+
elif any(term in domain_entities for term in ['payment', 'wallet', 'transaction']):
|
|
642
|
+
domain = "Financial Application"
|
|
643
|
+
elif any(term in domain_entities for term in ['product', 'inventory', 'catalog']):
|
|
644
|
+
domain = "E-commerce/Inventory System"
|
|
645
|
+
elif len(domain_entities) > 0:
|
|
646
|
+
domain = "Business Application"
|
|
647
|
+
|
|
648
|
+
# Determine architectural style
|
|
649
|
+
arch_style = "Modular"
|
|
650
|
+
if len(self.analysis.execution_flows) > 2:
|
|
651
|
+
arch_style = "Service-oriented"
|
|
652
|
+
if any('service' in symbol.name.lower() for symbol in self.analysis.symbols):
|
|
653
|
+
arch_style = "Service Layer Architecture"
|
|
654
|
+
|
|
655
|
+
# Generate description
|
|
656
|
+
entity_count = len(self.analysis.domain_entities)
|
|
657
|
+
service_count = len([s for s in self.analysis.symbols if 'service' in s.name.lower() and s.type == 'class'])
|
|
658
|
+
|
|
659
|
+
description = f"This project appears to be a {domain.lower()} implementing a {arch_style.lower()}."
|
|
660
|
+
if entity_count > 0:
|
|
661
|
+
description += f" The system defines {entity_count} core domain entities"
|
|
662
|
+
if service_count > 0:
|
|
663
|
+
description += f" with {service_count} service classes handling business logic"
|
|
664
|
+
description += "."
|
|
665
|
+
|
|
666
|
+
if len(self.analysis.execution_flows) > 0:
|
|
667
|
+
description += f" Analysis identified {len(self.analysis.execution_flows)} distinct execution flows through the system."
|
|
668
|
+
|
|
669
|
+
return f"""
|
|
670
|
+
<div class="project-summary">
|
|
671
|
+
<h2>Project Summary</h2>
|
|
672
|
+
<p>{description}</p>
|
|
673
|
+
</div>
|
|
674
|
+
"""
|
|
675
|
+
|
|
676
|
+
def _generate_summary_metrics(self) -> str:
|
|
677
|
+
"""Generate summary metrics cards."""
|
|
678
|
+
return f"""
|
|
679
|
+
<div class="metrics-grid">
|
|
680
|
+
<div class="metric-card">
|
|
681
|
+
<div class="metric-value">{self.analysis.total_files}</div>
|
|
682
|
+
<div class="metric-title">Total Files</div>
|
|
683
|
+
<div class="metric-caption">Across {len(self.analysis.languages)} languages</div>
|
|
684
|
+
</div>
|
|
685
|
+
<div class="metric-card">
|
|
686
|
+
<div class="metric-value">{self.analysis.total_lines:,}</div>
|
|
687
|
+
<div class="metric-title">Lines of Code</div>
|
|
688
|
+
<div class="metric-caption">Excluding comments and blanks</div>
|
|
689
|
+
</div>
|
|
690
|
+
<div class="metric-card">
|
|
691
|
+
<div class="metric-value">{len(self.analysis.symbols)}</div>
|
|
692
|
+
<div class="metric-title">Code Symbols</div>
|
|
693
|
+
<div class="metric-caption">Functions, classes, and methods</div>
|
|
694
|
+
</div>
|
|
695
|
+
<div class="metric-card">
|
|
696
|
+
<div class="metric-value">{self.analysis.complexity_score:.1f}</div>
|
|
697
|
+
<div class="metric-title">Complexity Score</div>
|
|
698
|
+
<div class="metric-caption">Based on call relationships</div>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
"""
|
|
702
|
+
|
|
703
|
+
def _generate_architecture(self) -> str:
|
|
704
|
+
"""Generate architecture section."""
|
|
705
|
+
return f"""
|
|
706
|
+
<div class="section" id="architecture">
|
|
707
|
+
<h2>Architecture</h2>
|
|
708
|
+
<p>The codebase follows a modular architecture with {len(self.analysis.symbols)} defined symbols
|
|
709
|
+
and {len(self.analysis.call_relations)} call relationships.</p>
|
|
710
|
+
|
|
711
|
+
<h3>Key Statistics</h3>
|
|
712
|
+
<ul>
|
|
713
|
+
<li>Functions: {len([s for s in self.analysis.symbols if s.type == 'function'])}</li>
|
|
714
|
+
<li>Classes: {len([s for s in self.analysis.symbols if s.type == 'class'])}</li>
|
|
715
|
+
<li>Methods: {len([s for s in self.analysis.symbols if s.type == 'method'])}</li>
|
|
716
|
+
<li>Domain Entities: {len(self.analysis.domain_entities)}</li>
|
|
717
|
+
</ul>
|
|
718
|
+
</div>
|
|
719
|
+
"""
|
|
720
|
+
|
|
721
|
+
def _generate_directory_structure(self) -> str:
|
|
722
|
+
"""Generate directory structure section."""
|
|
723
|
+
tree_text = self._render_directory_tree_text(self.analysis.directory_tree, self.analysis.root_path.name)
|
|
724
|
+
|
|
725
|
+
return f"""
|
|
726
|
+
<div class="section">
|
|
727
|
+
<h2>Directory Structure</h2>
|
|
728
|
+
<div class="directory-tree">{tree_text}</div>
|
|
729
|
+
</div>
|
|
730
|
+
"""
|
|
731
|
+
|
|
732
|
+
def _render_directory_tree_text(self, tree: Dict, root_name: str, prefix: str = "") -> str:
|
|
733
|
+
"""Render directory tree as plain text."""
|
|
734
|
+
if not tree:
|
|
735
|
+
return f"{root_name}/\n"
|
|
736
|
+
|
|
737
|
+
lines = [f"{root_name}/"]
|
|
738
|
+
items = [(k, v) for k, v in tree.items() if k != '_files']
|
|
739
|
+
files = tree.get('_files', [])
|
|
740
|
+
|
|
741
|
+
# Add directories
|
|
742
|
+
for i, (key, value) in enumerate(items):
|
|
743
|
+
is_last_dir = i == len(items) - 1 and not files
|
|
744
|
+
current_prefix = "└── " if is_last_dir else "├── "
|
|
745
|
+
lines.append(f"{prefix}{current_prefix}{key}/")
|
|
746
|
+
|
|
747
|
+
if isinstance(value, dict):
|
|
748
|
+
next_prefix = prefix + (" " if is_last_dir else "│ ")
|
|
749
|
+
subtree = self._render_directory_tree_text(value, "", next_prefix)
|
|
750
|
+
lines.extend(subtree.strip().split('\n')[1:]) # Skip the root line
|
|
751
|
+
|
|
752
|
+
# Add files
|
|
753
|
+
for i, file in enumerate(files):
|
|
754
|
+
is_last = i == len(files) - 1
|
|
755
|
+
file_prefix = "└── " if is_last else "├── "
|
|
756
|
+
lines.append(f"{prefix}{file_prefix}{file}")
|
|
757
|
+
|
|
758
|
+
return '\n'.join(lines)
|
|
759
|
+
|
|
760
|
+
def _generate_key_components(self) -> str:
|
|
761
|
+
"""Generate key components section with separate tables."""
|
|
762
|
+
classes = [s for s in self.analysis.symbols if s.type == 'class']
|
|
763
|
+
functions = [s for s in self.analysis.symbols if s.type == 'function']
|
|
764
|
+
methods = [s for s in self.analysis.symbols if s.type == 'method']
|
|
765
|
+
|
|
766
|
+
return f"""
|
|
767
|
+
<div class="section" id="key-components">
|
|
768
|
+
<h2>Key Components</h2>
|
|
769
|
+
<div class="component-tables">
|
|
770
|
+
{self._generate_component_table("Classes", classes)}
|
|
771
|
+
{self._generate_component_table("Functions", functions)}
|
|
772
|
+
{self._generate_component_table("Methods", methods)}
|
|
773
|
+
</div>
|
|
774
|
+
</div>
|
|
775
|
+
"""
|
|
776
|
+
|
|
777
|
+
def _generate_component_table(self, title: str, symbols: list) -> str:
|
|
778
|
+
"""Generate a component table for a specific symbol type."""
|
|
779
|
+
if not symbols:
|
|
780
|
+
return f"""
|
|
781
|
+
<div>
|
|
782
|
+
<h3>{title}</h3>
|
|
783
|
+
<p>No {title.lower()} found.</p>
|
|
784
|
+
</div>
|
|
785
|
+
"""
|
|
786
|
+
|
|
787
|
+
# Limit to top 10 for readability
|
|
788
|
+
symbols = symbols[:10]
|
|
789
|
+
|
|
790
|
+
table_rows = ""
|
|
791
|
+
for symbol in symbols:
|
|
792
|
+
rel_path = symbol.file_path.relative_to(self.analysis.root_path)
|
|
793
|
+
docstring = symbol.docstring[:50] + "..." if symbol.docstring and len(symbol.docstring) > 50 else (symbol.docstring or "")
|
|
794
|
+
|
|
795
|
+
table_rows += f"""
|
|
796
|
+
<tr>
|
|
797
|
+
<td><code>{symbol.name}</code></td>
|
|
798
|
+
<td>{rel_path}</td>
|
|
799
|
+
<td>{symbol.line_number}</td>
|
|
800
|
+
<td>{docstring}</td>
|
|
801
|
+
</tr>
|
|
802
|
+
"""
|
|
803
|
+
|
|
804
|
+
return f"""
|
|
805
|
+
<div>
|
|
806
|
+
<h3>{title}</h3>
|
|
807
|
+
<table class="table">
|
|
808
|
+
<thead>
|
|
809
|
+
<tr>
|
|
810
|
+
<th>Name</th>
|
|
811
|
+
<th>File</th>
|
|
812
|
+
<th>Line</th>
|
|
813
|
+
<th>Description</th>
|
|
814
|
+
</tr>
|
|
815
|
+
</thead>
|
|
816
|
+
<tbody>
|
|
817
|
+
{table_rows}
|
|
818
|
+
</tbody>
|
|
819
|
+
</table>
|
|
820
|
+
</div>
|
|
821
|
+
"""
|
|
822
|
+
|
|
823
|
+
def _generate_core_logic(self) -> str:
|
|
824
|
+
"""Generate core logic section with flow timeline."""
|
|
825
|
+
if not self.analysis.execution_flows:
|
|
826
|
+
return f"""
|
|
827
|
+
<div class="section">
|
|
828
|
+
<h2>Core Logic</h2>
|
|
829
|
+
<p>No execution flows detected in the codebase.</p>
|
|
830
|
+
</div>
|
|
831
|
+
"""
|
|
832
|
+
|
|
833
|
+
flow_cards = ""
|
|
834
|
+
for flow in self.analysis.execution_flows:
|
|
835
|
+
steps_text = " → ".join(flow.steps[:5])
|
|
836
|
+
if len(flow.steps) > 5:
|
|
837
|
+
steps_text += f" → ... (+{len(flow.steps) - 5} more)"
|
|
838
|
+
|
|
839
|
+
flow_cards += f"""
|
|
840
|
+
<div class="flow-card">
|
|
841
|
+
<div class="flow-name">{flow.name.replace('_', ' ').title()}</div>
|
|
842
|
+
<div class="flow-description">{flow.description}</div>
|
|
843
|
+
<div class="flow-steps">{steps_text}</div>
|
|
844
|
+
</div>
|
|
845
|
+
"""
|
|
846
|
+
|
|
847
|
+
return f"""
|
|
848
|
+
<div class="section" id="core-logic">
|
|
849
|
+
<h2>Core Logic</h2>
|
|
850
|
+
<div class="flow-timeline">
|
|
851
|
+
{flow_cards}
|
|
852
|
+
</div>
|
|
853
|
+
</div>
|
|
854
|
+
"""
|
|
855
|
+
|
|
856
|
+
def _generate_dependencies(self) -> str:
|
|
857
|
+
"""Generate dependencies section with usage bars."""
|
|
858
|
+
if not self.analysis.imports:
|
|
859
|
+
return f"""
|
|
860
|
+
<div class="section">
|
|
861
|
+
<h2>Dependencies</h2>
|
|
862
|
+
<p>No imports detected.</p>
|
|
863
|
+
</div>
|
|
864
|
+
"""
|
|
865
|
+
|
|
866
|
+
# Group imports by module
|
|
867
|
+
import_counts = {}
|
|
868
|
+
for imp in self.analysis.imports:
|
|
869
|
+
if imp.module not in import_counts:
|
|
870
|
+
import_counts[imp.module] = 0
|
|
871
|
+
import_counts[imp.module] += 1
|
|
872
|
+
|
|
873
|
+
# Sort by frequency
|
|
874
|
+
top_imports = sorted(import_counts.items(), key=lambda x: x[1], reverse=True)[:10]
|
|
875
|
+
max_count = max(count for _, count in top_imports) if top_imports else 1
|
|
876
|
+
|
|
877
|
+
dependency_items = ""
|
|
878
|
+
for module, count in top_imports:
|
|
879
|
+
bar_width = (count / max_count) * 100
|
|
880
|
+
dependency_items += f"""
|
|
881
|
+
<div class="dependency-item">
|
|
882
|
+
<div class="dependency-name">{module}</div>
|
|
883
|
+
<div class="dependency-bar">
|
|
884
|
+
<div class="dependency-count">{count}</div>
|
|
885
|
+
<div class="dependency-rail">
|
|
886
|
+
<div class="bar" style="width: {bar_width}%;"></div>
|
|
887
|
+
</div>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
"""
|
|
891
|
+
|
|
892
|
+
return f"""
|
|
893
|
+
<div class="section">
|
|
894
|
+
<h2>Dependencies</h2>
|
|
895
|
+
<div>
|
|
896
|
+
{dependency_items}
|
|
897
|
+
</div>
|
|
898
|
+
</div>
|
|
899
|
+
"""
|
|
900
|
+
|
|
901
|
+
def _generate_data_flow(self) -> str:
|
|
902
|
+
"""Generate data flow section."""
|
|
903
|
+
entities_html = ""
|
|
904
|
+
for entity in self.analysis.domain_entities:
|
|
905
|
+
fields_str = ", ".join(entity.fields[:5])
|
|
906
|
+
if len(entity.fields) > 5:
|
|
907
|
+
fields_str += f" ... (+{len(entity.fields) - 5} more)"
|
|
908
|
+
|
|
909
|
+
entities_html += f"""
|
|
910
|
+
<div class="info">
|
|
911
|
+
<strong>{entity.name}</strong> ({entity.type})<br>
|
|
912
|
+
Fields: {fields_str}<br>
|
|
913
|
+
File: {entity.file_path.relative_to(self.analysis.root_path)}
|
|
914
|
+
</div>
|
|
915
|
+
"""
|
|
916
|
+
|
|
917
|
+
return f"""
|
|
918
|
+
<div class="section" id="data-flow">
|
|
919
|
+
<h2>Data Flow</h2>
|
|
920
|
+
<p>Identified {len(self.analysis.domain_entities)} domain entities:</p>
|
|
921
|
+
{entities_html}
|
|
922
|
+
</div>
|
|
923
|
+
"""
|
|
924
|
+
|
|
925
|
+
def _generate_risks(self) -> str:
|
|
926
|
+
"""Generate risks and technical debt section."""
|
|
927
|
+
risks = []
|
|
928
|
+
|
|
929
|
+
if self.analysis.complexity_score > 70:
|
|
930
|
+
risks.append({
|
|
931
|
+
'severity': 'high',
|
|
932
|
+
'reason': 'High complexity score indicates potential maintainability issues',
|
|
933
|
+
'evidence': f'Complexity score: {self.analysis.complexity_score:.1f}/100'
|
|
934
|
+
})
|
|
935
|
+
elif self.analysis.complexity_score > 40:
|
|
936
|
+
risks.append({
|
|
937
|
+
'severity': 'medium',
|
|
938
|
+
'reason': 'Moderate complexity may impact long-term maintainability',
|
|
939
|
+
'evidence': f'Complexity score: {self.analysis.complexity_score:.1f}/100'
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
if len(self.analysis.entry_points) == 0:
|
|
943
|
+
risks.append({
|
|
944
|
+
'severity': 'medium',
|
|
945
|
+
'reason': 'No clear entry points detected',
|
|
946
|
+
'evidence': 'May indicate unclear application structure or missing main files'
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
if len(self.analysis.execution_flows) < 2:
|
|
950
|
+
risks.append({
|
|
951
|
+
'severity': 'low',
|
|
952
|
+
'reason': 'Limited execution flows detected',
|
|
953
|
+
'evidence': f'Only {len(self.analysis.execution_flows)} flows identified - may indicate simple codebase or incomplete analysis'
|
|
954
|
+
})
|
|
955
|
+
|
|
956
|
+
# If no risks, add a low-severity default
|
|
957
|
+
if not risks:
|
|
958
|
+
risks.append({
|
|
959
|
+
'severity': 'low',
|
|
960
|
+
'reason': 'No significant risks detected',
|
|
961
|
+
'evidence': 'Codebase appears well-structured based on current analysis'
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
risk_items = ""
|
|
965
|
+
for risk in risks:
|
|
966
|
+
risk_items += f"""
|
|
967
|
+
<div class="risk-item {risk['severity']}">
|
|
968
|
+
<div class="risk-header">
|
|
969
|
+
<span class="risk-severity {risk['severity']}">{risk['severity']}</span>
|
|
970
|
+
<span class="risk-reason">{risk['reason']}</span>
|
|
971
|
+
</div>
|
|
972
|
+
<div class="risk-evidence">{risk['evidence']}</div>
|
|
973
|
+
</div>
|
|
974
|
+
"""
|
|
975
|
+
|
|
976
|
+
return f"""
|
|
977
|
+
<div class="section">
|
|
978
|
+
<h2>Risks / Technical Debt</h2>
|
|
979
|
+
{risk_items}
|
|
980
|
+
</div>
|
|
981
|
+
"""
|
|
982
|
+
|
|
983
|
+
def _generate_recommendations(self) -> str:
|
|
984
|
+
"""Generate recommendations section."""
|
|
985
|
+
recommendations = []
|
|
986
|
+
|
|
987
|
+
if self.analysis.complexity_score > 50:
|
|
988
|
+
recommendations.append({
|
|
989
|
+
'priority': 'high',
|
|
990
|
+
'text': 'Consider refactoring complex functions to improve maintainability',
|
|
991
|
+
'rationale': f'Current complexity score of {self.analysis.complexity_score:.1f} suggests potential maintainability challenges',
|
|
992
|
+
'action': 'Review functions with high cyclomatic complexity',
|
|
993
|
+
'link': '#key-components'
|
|
994
|
+
})
|
|
995
|
+
|
|
996
|
+
if len(self.analysis.domain_entities) > 0:
|
|
997
|
+
recommendations.append({
|
|
998
|
+
'priority': 'medium',
|
|
999
|
+
'text': 'Document domain entities and their relationships',
|
|
1000
|
+
'rationale': f'Found {len(self.analysis.domain_entities)} domain entities that would benefit from clear documentation',
|
|
1001
|
+
'action': 'Create entity relationship diagrams',
|
|
1002
|
+
'link': '#data-flow'
|
|
1003
|
+
})
|
|
1004
|
+
|
|
1005
|
+
if len(self.analysis.execution_flows) > 0:
|
|
1006
|
+
recommendations.append({
|
|
1007
|
+
'priority': 'medium',
|
|
1008
|
+
'text': 'Create sequence diagrams for critical execution flows',
|
|
1009
|
+
'rationale': f'Identified {len(self.analysis.execution_flows)} execution flows that could be visualized for better understanding',
|
|
1010
|
+
'action': 'Document flow sequences',
|
|
1011
|
+
'link': '#core-logic'
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
recommendations.append({
|
|
1015
|
+
'priority': 'low',
|
|
1016
|
+
'text': 'Add comprehensive unit tests for core business logic',
|
|
1017
|
+
'rationale': 'Testing coverage analysis not performed - recommended for production systems',
|
|
1018
|
+
'action': 'Set up testing framework and write tests',
|
|
1019
|
+
'link': '#key-components'
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
recommendations.append({
|
|
1023
|
+
'priority': 'low',
|
|
1024
|
+
'text': 'Consider implementing code documentation standards',
|
|
1025
|
+
'rationale': 'Consistent documentation improves maintainability and onboarding',
|
|
1026
|
+
'action': 'Establish documentation guidelines',
|
|
1027
|
+
'link': '#architecture'
|
|
1028
|
+
})
|
|
1029
|
+
|
|
1030
|
+
rec_items = ""
|
|
1031
|
+
for rec in recommendations:
|
|
1032
|
+
rec_items += f"""
|
|
1033
|
+
<div class="recommendation-item">
|
|
1034
|
+
<div class="recommendation-header">
|
|
1035
|
+
<span class="recommendation-priority {rec['priority']}">{rec['priority']}</span>
|
|
1036
|
+
<span class="recommendation-text">{rec['text']}</span>
|
|
1037
|
+
</div>
|
|
1038
|
+
<div class="recommendation-rationale">{rec['rationale']}</div>
|
|
1039
|
+
<div class="recommendation-actions">
|
|
1040
|
+
<a href="{rec['link']}" class="recommendation-link">
|
|
1041
|
+
{rec['action']} →
|
|
1042
|
+
</a>
|
|
1043
|
+
</div>
|
|
1044
|
+
</div>
|
|
1045
|
+
"""
|
|
1046
|
+
|
|
1047
|
+
return f"""
|
|
1048
|
+
<div class="section" id="recommendations">
|
|
1049
|
+
<h2>Recommendations</h2>
|
|
1050
|
+
{rec_items}
|
|
1051
|
+
</div>
|
|
1052
|
+
"""
|