cortexcode 0.2.2__py3-none-any.whl → 0.4.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.
- cortexcode/advanced_analysis.py +816 -0
- cortexcode/docs/__init__.py +19 -0
- cortexcode/docs/generator.py +573 -0
- cortexcode/docs/html_generators.py +114 -0
- cortexcode/docs/javascript.py +373 -0
- cortexcode/docs/templates.py +174 -0
- cortexcode/docs.py +38 -1266
- cortexcode/indexer.py +28 -4
- cortexcode/mcp_server.py +142 -0
- {cortexcode-0.2.2.dist-info → cortexcode-0.4.0.dist-info}/METADATA +80 -4
- cortexcode-0.4.0.dist-info/RECORD +27 -0
- cortexcode-0.2.2.dist-info/RECORD +0 -21
- {cortexcode-0.2.2.dist-info → cortexcode-0.4.0.dist-info}/WHEEL +0 -0
- {cortexcode-0.2.2.dist-info → cortexcode-0.4.0.dist-info}/entry_points.txt +0 -0
- {cortexcode-0.2.2.dist-info → cortexcode-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {cortexcode-0.2.2.dist-info → cortexcode-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Docs Generator Package - Generate project documentation from index."""
|
|
2
|
+
|
|
3
|
+
from cortexcode.docs.generator import (
|
|
4
|
+
generate_all_docs,
|
|
5
|
+
generate_readme,
|
|
6
|
+
generate_api_docs,
|
|
7
|
+
generate_structure_docs,
|
|
8
|
+
generate_flow_docs,
|
|
9
|
+
generate_html_docs,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"generate_all_docs",
|
|
14
|
+
"generate_readme",
|
|
15
|
+
"generate_api_docs",
|
|
16
|
+
"generate_structure_docs",
|
|
17
|
+
"generate_flow_docs",
|
|
18
|
+
"generate_html_docs",
|
|
19
|
+
]
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
"""Main documentation generator - orchestrates all doc generation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
from cortexcode.docs.templates import CSS_TEMPLATE, TYPE_COLORS, LANG_COLORS, D3_CDN_URL
|
|
8
|
+
from cortexcode.docs.javascript import JS_TEMPLATE
|
|
9
|
+
from cortexcode.docs.html_generators import (
|
|
10
|
+
generate_tree_html,
|
|
11
|
+
generate_symbols_html,
|
|
12
|
+
generate_imports_html,
|
|
13
|
+
generate_exports_html,
|
|
14
|
+
generate_routes_html,
|
|
15
|
+
generate_entities_html,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
D3_LOCAL_FILE = "d3.min.js"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _ensure_d3_local(output_dir: Path) -> str:
|
|
22
|
+
"""Ensure D3.js is available locally. Returns the script src to use."""
|
|
23
|
+
local_path = output_dir / D3_LOCAL_FILE
|
|
24
|
+
if local_path.exists() and local_path.stat().st_size > 100_000:
|
|
25
|
+
return D3_LOCAL_FILE
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import urllib.request
|
|
29
|
+
urllib.request.urlretrieve(D3_CDN_URL, str(local_path))
|
|
30
|
+
if local_path.exists() and local_path.stat().st_size > 100_000:
|
|
31
|
+
return D3_LOCAL_FILE
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
return D3_CDN_URL
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def generate_all_docs(index_path: Path, output_dir: Path) -> None:
|
|
39
|
+
"""Generate all documentation files from the index."""
|
|
40
|
+
output_dir = Path(output_dir)
|
|
41
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
index = json.loads(index_path.read_text(encoding="utf-8"))
|
|
44
|
+
|
|
45
|
+
d3_src = _ensure_d3_local(output_dir)
|
|
46
|
+
|
|
47
|
+
generate_readme(index, output_dir / "README.md")
|
|
48
|
+
generate_api_docs(index, output_dir / "API.md")
|
|
49
|
+
generate_structure_docs(index, output_dir / "STRUCTURE.md")
|
|
50
|
+
generate_flow_docs(index, output_dir / "FLOWS.md")
|
|
51
|
+
generate_html_docs(index, output_dir / "index.html", d3_src=d3_src)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def generate_readme(index: dict, output_path: Path) -> None:
|
|
55
|
+
"""Generate project README."""
|
|
56
|
+
files = index.get("files", {})
|
|
57
|
+
call_graph = index.get("call_graph", {})
|
|
58
|
+
|
|
59
|
+
directories = defaultdict(list)
|
|
60
|
+
for rel_path in files.keys():
|
|
61
|
+
parts = Path(rel_path).parts
|
|
62
|
+
if len(parts) > 1:
|
|
63
|
+
directories[parts[0]].append(rel_path)
|
|
64
|
+
|
|
65
|
+
lines = [
|
|
66
|
+
"# Project Documentation",
|
|
67
|
+
"",
|
|
68
|
+
"## Overview",
|
|
69
|
+
"",
|
|
70
|
+
f"**Project Root:** `{index.get('project_root', 'N/A')}`",
|
|
71
|
+
f"**Last Indexed:** {index.get('last_indexed', 'N/A')}",
|
|
72
|
+
"",
|
|
73
|
+
"## Key Modules",
|
|
74
|
+
"",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
for dir_name, dir_files in sorted(directories.items()):
|
|
78
|
+
lines.append(f"- `{dir_name}/` — {len(dir_files)} files")
|
|
79
|
+
|
|
80
|
+
lines.extend([
|
|
81
|
+
"",
|
|
82
|
+
"## Entry Points",
|
|
83
|
+
"",
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
for rel_path in files.keys():
|
|
87
|
+
if Path(rel_path).name in ("main.py", "app.py", "server.py", "cli.py", "__main__.py"):
|
|
88
|
+
lines.append(f"- `{rel_path}`")
|
|
89
|
+
|
|
90
|
+
if not any(Path(p).name in ("main.py", "app.py", "server.py", "cli.py", "__main__.py") for p in files.keys()):
|
|
91
|
+
lines.append(" (No obvious entry points found)")
|
|
92
|
+
|
|
93
|
+
lines.extend([
|
|
94
|
+
"",
|
|
95
|
+
"## Symbol Count",
|
|
96
|
+
"",
|
|
97
|
+
f"- **Files:** {len(files)}",
|
|
98
|
+
f"- **Symbols:** {len(call_graph)}",
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
output_path.write_text("\n".join(lines), encoding="utf-8")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def generate_api_docs(index: dict, output_path: Path) -> None:
|
|
105
|
+
"""Generate API documentation."""
|
|
106
|
+
files = index.get("files", {})
|
|
107
|
+
|
|
108
|
+
lines = [
|
|
109
|
+
"# API Documentation",
|
|
110
|
+
"",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
for rel_path, file_data in sorted(files.items()):
|
|
114
|
+
symbols = file_data.get("symbols", []) if isinstance(file_data, dict) else file_data
|
|
115
|
+
if not symbols:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
lines.append(f"## {rel_path}")
|
|
119
|
+
lines.append("")
|
|
120
|
+
|
|
121
|
+
for sym in symbols:
|
|
122
|
+
name = sym.get("name", "unknown")
|
|
123
|
+
sym_type = sym.get("type", "function")
|
|
124
|
+
params = sym.get("params", [])
|
|
125
|
+
doc = sym.get("doc", "")
|
|
126
|
+
|
|
127
|
+
if sym_type == "class":
|
|
128
|
+
lines.append(f"### class `{name}`")
|
|
129
|
+
else:
|
|
130
|
+
params_str = ", ".join(params) if params else ""
|
|
131
|
+
lines.append(f"### `{name}({params_str})`")
|
|
132
|
+
|
|
133
|
+
lines.append("")
|
|
134
|
+
if doc:
|
|
135
|
+
lines.append(f"> {doc}")
|
|
136
|
+
lines.append("")
|
|
137
|
+
|
|
138
|
+
if sym.get("methods"):
|
|
139
|
+
lines.append("**Methods:**")
|
|
140
|
+
for method in sym.get("methods", []):
|
|
141
|
+
method_params = ", ".join(method.get("params", []))
|
|
142
|
+
lines.append(f"- `{method.get('name', '')}({method_params})`")
|
|
143
|
+
lines.append("")
|
|
144
|
+
|
|
145
|
+
output_path.write_text("\n".join(lines), encoding="utf-8")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def generate_structure_docs(index: dict, output_path: Path) -> None:
|
|
149
|
+
"""Generate project structure documentation."""
|
|
150
|
+
files = index.get("files", {})
|
|
151
|
+
|
|
152
|
+
lines = [
|
|
153
|
+
"# Project Structure",
|
|
154
|
+
"",
|
|
155
|
+
"```",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
for rel_path in sorted(files.keys()):
|
|
159
|
+
lines.append(rel_path)
|
|
160
|
+
|
|
161
|
+
lines.append("```")
|
|
162
|
+
|
|
163
|
+
output_path.write_text("\n".join(lines), encoding="utf-8")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def generate_flow_docs(index: dict, output_path: Path) -> None:
|
|
167
|
+
"""Generate call flow documentation."""
|
|
168
|
+
call_graph = index.get("call_graph", {})
|
|
169
|
+
|
|
170
|
+
lines = [
|
|
171
|
+
"# Call Flows",
|
|
172
|
+
"",
|
|
173
|
+
"## Call Graph",
|
|
174
|
+
"",
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
for symbol, calls in sorted(call_graph.items()):
|
|
178
|
+
if calls:
|
|
179
|
+
lines.append(f"### {symbol}")
|
|
180
|
+
lines.append("")
|
|
181
|
+
for call in calls:
|
|
182
|
+
lines.append(f" → {call}")
|
|
183
|
+
lines.append("")
|
|
184
|
+
|
|
185
|
+
if not any(call_graph.values()):
|
|
186
|
+
lines.append("*No call relationships found.*")
|
|
187
|
+
|
|
188
|
+
output_path.write_text("\n".join(lines), encoding="utf-8")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def generate_html_docs(index: dict, output_path: Path, d3_src: str = D3_CDN_URL) -> None:
|
|
192
|
+
"""Generate interactive HTML documentation."""
|
|
193
|
+
files = index.get("files", {})
|
|
194
|
+
call_graph = index.get("call_graph", {})
|
|
195
|
+
file_deps = index.get("file_dependencies", {})
|
|
196
|
+
project_root = index.get("project_root", "")
|
|
197
|
+
last_indexed = index.get("last_indexed", "")
|
|
198
|
+
|
|
199
|
+
all_symbols = []
|
|
200
|
+
file_tree = {}
|
|
201
|
+
framework_counts = {
|
|
202
|
+
"react": 0, "react-native": 0, "expo": 0,
|
|
203
|
+
"angular": 0, "nextjs": 0, "nestjs": 0, "express": 0,
|
|
204
|
+
"flutter": 0, "compose": 0, "android": 0,
|
|
205
|
+
"swiftui": 0, "uikit": 0, "ios": 0,
|
|
206
|
+
"spring": 0, "fastapi": 0, "django": 0, "flask": 0, "aspnet": 0,
|
|
207
|
+
}
|
|
208
|
+
language_counts = {}
|
|
209
|
+
type_counts = {}
|
|
210
|
+
all_imports = []
|
|
211
|
+
all_exports = []
|
|
212
|
+
all_api_routes = []
|
|
213
|
+
all_entities = []
|
|
214
|
+
files_with_most_symbols = []
|
|
215
|
+
|
|
216
|
+
for rel_path, file_data in files.items():
|
|
217
|
+
symbols = file_data.get("symbols", []) if isinstance(file_data, dict) else file_data
|
|
218
|
+
imports = file_data.get("imports", []) if isinstance(file_data, dict) else []
|
|
219
|
+
exports = file_data.get("exports", []) if isinstance(file_data, dict) else []
|
|
220
|
+
api_routes = file_data.get("api_routes", []) if isinstance(file_data, dict) else []
|
|
221
|
+
entities = file_data.get("entities", []) if isinstance(file_data, dict) else []
|
|
222
|
+
|
|
223
|
+
ext = Path(rel_path).suffix
|
|
224
|
+
language_counts[ext] = language_counts.get(ext, 0) + 1
|
|
225
|
+
|
|
226
|
+
parts = rel_path.replace("\\", "/").split("/")
|
|
227
|
+
current = file_tree
|
|
228
|
+
for part in parts[:-1]:
|
|
229
|
+
if part not in current:
|
|
230
|
+
current[part] = {}
|
|
231
|
+
current = current[part]
|
|
232
|
+
if parts[-1] not in current:
|
|
233
|
+
current[parts[-1]] = symbols
|
|
234
|
+
|
|
235
|
+
all_imports.extend([{"file": rel_path, **imp} for imp in imports])
|
|
236
|
+
all_exports.extend([{"file": rel_path, **exp} for exp in exports])
|
|
237
|
+
all_api_routes.extend([{"file": rel_path, **route} for route in api_routes])
|
|
238
|
+
all_entities.extend([{"file": rel_path, **ent} for ent in entities])
|
|
239
|
+
|
|
240
|
+
files_with_most_symbols.append({"file": rel_path, "count": len(symbols)})
|
|
241
|
+
|
|
242
|
+
for sym in symbols:
|
|
243
|
+
sym["file"] = rel_path
|
|
244
|
+
all_symbols.append(sym)
|
|
245
|
+
t = sym.get("type", "unknown")
|
|
246
|
+
type_counts[t] = type_counts.get(t, 0) + 1
|
|
247
|
+
|
|
248
|
+
fw = sym.get("framework")
|
|
249
|
+
if fw:
|
|
250
|
+
for key in framework_counts:
|
|
251
|
+
if key in fw:
|
|
252
|
+
framework_counts[key] += 1
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
files_with_most_symbols.sort(key=lambda x: x["count"], reverse=True)
|
|
256
|
+
|
|
257
|
+
non_empty_calls = sum(1 for v in call_graph.values() if v)
|
|
258
|
+
total_call_edges = sum(len(v) for v in call_graph.values() if v)
|
|
259
|
+
top_callers = sorted(
|
|
260
|
+
[(k, len(v)) for k, v in call_graph.items() if v],
|
|
261
|
+
key=lambda x: x[1], reverse=True
|
|
262
|
+
)[:10]
|
|
263
|
+
|
|
264
|
+
project_name = Path(project_root).name
|
|
265
|
+
|
|
266
|
+
# Pre-build HTML fragments
|
|
267
|
+
fw_cards_html = ""
|
|
268
|
+
if any(framework_counts.values()):
|
|
269
|
+
fw_items = "".join(
|
|
270
|
+
f'<div style="background:var(--bg);padding:12px 20px;border-radius:var(--radius-sm);text-align:center;">'
|
|
271
|
+
f'<div style="font-size:24px;font-weight:700;color:var(--accent);">{c}</div>'
|
|
272
|
+
f'<div style="font-size:12px;color:var(--text3);margin-top:2px;">{fw}</div></div>'
|
|
273
|
+
for fw, c in framework_counts.items() if c > 0
|
|
274
|
+
)
|
|
275
|
+
fw_cards_html = (
|
|
276
|
+
'<div class="card"><div class="card-title">Detected Frameworks</div>'
|
|
277
|
+
'<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:12px;">'
|
|
278
|
+
f'{fw_items}</div></div>'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
filter_tabs_html = "".join(
|
|
282
|
+
f'<div class="filter-tab" onclick="filterByType(\'{t}\', this)">{t.title()} ({c})</div>'
|
|
283
|
+
for t, c in sorted(type_counts.items(), key=lambda x: -x[1])
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
top_files_rows = "".join(
|
|
287
|
+
f'<tr><td style="color:var(--text2);font-size:12px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="{f["file"]}">{f["file"]}</td>'
|
|
288
|
+
f'<td style="font-weight:600;color:var(--accent);">{f["count"]}</td>'
|
|
289
|
+
f'<td><div class="bar" style="width:{min(100, f["count"] * 100 // max(1, files_with_most_symbols[0]["count"] if files_with_most_symbols else 1))}%;"></div></td></tr>'
|
|
290
|
+
for f in files_with_most_symbols[:8]
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
top_callers_rows = "".join(
|
|
294
|
+
f'<tr><td style="color:var(--accent);font-size:13px;cursor:pointer;" onclick="highlightGraphNode(\'{name}\')">{name}</td>'
|
|
295
|
+
f'<td style="font-weight:600;">{count}</td>'
|
|
296
|
+
f'<td><div class="bar" style="width:{min(100, count * 100 // max(1, top_callers[0][1] if top_callers else 1))}%;background:var(--accent2);"></div></td></tr>'
|
|
297
|
+
for name, count in top_callers[:8]
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# JSON data for JavaScript
|
|
301
|
+
search_data_json = json.dumps([
|
|
302
|
+
{"name": s.get("name",""), "type": s.get("type",""), "file": s.get("file",""), "line": s.get("line",0), "doc": (s.get("doc","") or "")[:60], "params": s.get("params",[])[:3]}
|
|
303
|
+
for s in all_symbols if isinstance(s, dict)
|
|
304
|
+
][:600])
|
|
305
|
+
call_graph_json = json.dumps(call_graph)
|
|
306
|
+
file_deps_json = json.dumps(file_deps)
|
|
307
|
+
type_counts_json = json.dumps(type_counts)
|
|
308
|
+
lang_counts_json = json.dumps(language_counts)
|
|
309
|
+
type_colors_json = json.dumps(TYPE_COLORS)
|
|
310
|
+
lang_colors_json = json.dumps(LANG_COLORS)
|
|
311
|
+
|
|
312
|
+
# Build JavaScript with data
|
|
313
|
+
js_code = JS_TEMPLATE.format(
|
|
314
|
+
call_graph_json=call_graph_json,
|
|
315
|
+
file_deps_json=file_deps_json,
|
|
316
|
+
type_counts_json=type_counts_json,
|
|
317
|
+
lang_counts_json=lang_counts_json,
|
|
318
|
+
type_colors_json=type_colors_json,
|
|
319
|
+
lang_colors_json=lang_colors_json,
|
|
320
|
+
search_data_json=search_data_json,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
html = f"""<!DOCTYPE html>
|
|
324
|
+
<html lang="en">
|
|
325
|
+
<head>
|
|
326
|
+
<meta charset="UTF-8">
|
|
327
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
328
|
+
<title>{project_name} — CortexCode Report</title>
|
|
329
|
+
<script src="{d3_src}"></script>
|
|
330
|
+
<style>{CSS_TEMPLATE}</style>
|
|
331
|
+
</head>
|
|
332
|
+
<body>
|
|
333
|
+
<div class="sidebar">
|
|
334
|
+
<div class="logo"><div class="logo-icon">C</div> CortexCode</div>
|
|
335
|
+
|
|
336
|
+
<div class="sidebar-stats">
|
|
337
|
+
<div class="sidebar-stat"><div class="sidebar-stat-val">{len(files)}</div><div class="sidebar-stat-lbl">Files</div></div>
|
|
338
|
+
<div class="sidebar-stat"><div class="sidebar-stat-val">{len(all_symbols)}</div><div class="sidebar-stat-lbl">Symbols</div></div>
|
|
339
|
+
<div class="sidebar-stat"><div class="sidebar-stat-val">{non_empty_calls}</div><div class="sidebar-stat-lbl">Linked</div></div>
|
|
340
|
+
<div class="sidebar-stat"><div class="sidebar-stat-val">{len(file_deps)}</div><div class="sidebar-stat-lbl">Deps</div></div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div class="nav-section">
|
|
344
|
+
<div class="nav-title">Dashboard</div>
|
|
345
|
+
<div class="nav-item active" onclick="showTab('overview', this)">📊 Overview</div>
|
|
346
|
+
<div class="nav-item" onclick="showTab('symbols', this)">⚡ Symbols <span class="badge">{len(all_symbols)}</span></div>
|
|
347
|
+
<div class="nav-item" onclick="showTab('graph', this)">🔗 Call Graph <span class="badge">{non_empty_calls}</span></div>
|
|
348
|
+
<div class="nav-item" onclick="showTab('deps', this)">🌐 File Deps <span class="badge">{len(file_deps)}</span></div>
|
|
349
|
+
</div>
|
|
350
|
+
<div class="nav-section">
|
|
351
|
+
<div class="nav-title">Explore</div>
|
|
352
|
+
<div class="nav-item" onclick="showTab('structure', this)">📁 Files <span class="badge">{len(files)}</span></div>
|
|
353
|
+
<div class="nav-item" onclick="showTab('imports', this)">📥 Imports <span class="badge">{len(all_imports)}</span></div>
|
|
354
|
+
<div class="nav-item" onclick="showTab('exports', this)">📤 Exports <span class="badge">{len(all_exports)}</span></div>
|
|
355
|
+
<div class="nav-item" onclick="showTab('routes', this)">🔌 API Routes <span class="badge">{len(all_api_routes)}</span></div>
|
|
356
|
+
<div class="nav-item" onclick="showTab('entities', this)">🗄️ Entities <span class="badge">{len(all_entities)}</span></div>
|
|
357
|
+
</div>
|
|
358
|
+
<div class="nav-section">
|
|
359
|
+
<div class="nav-title">Docs</div>
|
|
360
|
+
<div class="nav-item" onclick="window.open('README.md','_blank')">📖 README</div>
|
|
361
|
+
<div class="nav-item" onclick="window.open('API.md','_blank')">📑 API Docs</div>
|
|
362
|
+
<div class="nav-item" onclick="window.open('FLOWS.md','_blank')">🔀 Call Flows</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<div class="main">
|
|
367
|
+
<div class="header">
|
|
368
|
+
<div>
|
|
369
|
+
<h1 class="project-name">{project_name}</h1>
|
|
370
|
+
<p class="last-indexed">Indexed {last_indexed[:19] if last_indexed else 'N/A'} · {len(index.get("languages", []))} languages</p>
|
|
371
|
+
</div>
|
|
372
|
+
<div class="search-wrapper">
|
|
373
|
+
<span class="search-icon">🔍</span>
|
|
374
|
+
<input type="text" id="globalSearchInput" class="search-box" placeholder="Search symbols, files..." onkeyup="doGlobalSearch(this.value)" autocomplete="off">
|
|
375
|
+
<div class="search-results" id="searchResults"></div>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<!-- OVERVIEW TAB -->
|
|
380
|
+
<div id="overview" class="tab-content active">
|
|
381
|
+
<div class="dash-stats">
|
|
382
|
+
<div class="dash-stat">
|
|
383
|
+
<div class="dash-stat-icon">📄</div>
|
|
384
|
+
<div class="dash-stat-val">{len(files)}</div>
|
|
385
|
+
<div class="dash-stat-lbl">Source Files</div>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="dash-stat">
|
|
388
|
+
<div class="dash-stat-icon">⚡</div>
|
|
389
|
+
<div class="dash-stat-val">{len(all_symbols)}</div>
|
|
390
|
+
<div class="dash-stat-lbl">Symbols</div>
|
|
391
|
+
</div>
|
|
392
|
+
<div class="dash-stat">
|
|
393
|
+
<div class="dash-stat-icon">🔗</div>
|
|
394
|
+
<div class="dash-stat-val">{total_call_edges}</div>
|
|
395
|
+
<div class="dash-stat-lbl">Call Edges</div>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="dash-stat">
|
|
398
|
+
<div class="dash-stat-icon">🌐</div>
|
|
399
|
+
<div class="dash-stat-val">{len(file_deps)}</div>
|
|
400
|
+
<div class="dash-stat-lbl">File Dependencies</div>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<div class="charts-row">
|
|
405
|
+
<div class="card">
|
|
406
|
+
<div class="card-title">Symbol Types</div>
|
|
407
|
+
<div class="card-subtitle">{len(all_symbols)} symbols across {len(type_counts)} types</div>
|
|
408
|
+
<div class="chart-container">
|
|
409
|
+
<svg id="typeDonut" class="chart-svg" width="160" height="160"></svg>
|
|
410
|
+
<div class="chart-legend" id="typeLegend"></div>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="card">
|
|
414
|
+
<div class="card-title">Languages</div>
|
|
415
|
+
<div class="card-subtitle">{len(files)} files across {len(language_counts)} extensions</div>
|
|
416
|
+
<div class="chart-container">
|
|
417
|
+
<svg id="langDonut" class="chart-svg" width="160" height="160"></svg>
|
|
418
|
+
<div class="chart-legend" id="langLegend"></div>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
<div class="charts-row">
|
|
424
|
+
<div class="card">
|
|
425
|
+
<div class="card-title">Top Files by Symbols</div>
|
|
426
|
+
<table class="mini-table">
|
|
427
|
+
<thead><tr><th>File</th><th style="width:50px;">Count</th><th style="width:120px;"></th></tr></thead>
|
|
428
|
+
<tbody>{top_files_rows}</tbody>
|
|
429
|
+
</table>
|
|
430
|
+
</div>
|
|
431
|
+
<div class="card">
|
|
432
|
+
<div class="card-title">Top Callers</div>
|
|
433
|
+
<div class="card-subtitle">Functions that call the most other functions</div>
|
|
434
|
+
<table class="mini-table">
|
|
435
|
+
<thead><tr><th>Symbol</th><th style="width:50px;">Calls</th><th style="width:120px;"></th></tr></thead>
|
|
436
|
+
<tbody>{top_callers_rows}</tbody>
|
|
437
|
+
</table>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
{fw_cards_html}
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<!-- SYMBOLS TAB -->
|
|
445
|
+
<div id="symbols" class="tab-content">
|
|
446
|
+
<div class="card">
|
|
447
|
+
<div class="card-header">
|
|
448
|
+
<div>
|
|
449
|
+
<div class="card-title">All Symbols</div>
|
|
450
|
+
<div class="card-subtitle">{len(all_symbols)} symbols extracted from {len(files)} files</div>
|
|
451
|
+
</div>
|
|
452
|
+
<input type="text" class="search-box" style="width:260px;padding-left:14px;" placeholder="Filter symbols..." onkeyup="filterSymbols(this.value)">
|
|
453
|
+
</div>
|
|
454
|
+
<div class="filter-tabs" id="symbolFilterTabs">
|
|
455
|
+
<div class="filter-tab active" onclick="filterByType('all', this)">All ({len(all_symbols)})</div>
|
|
456
|
+
{filter_tabs_html}
|
|
457
|
+
</div>
|
|
458
|
+
<div class="symbol-grid" id="symbolGrid">
|
|
459
|
+
{generate_symbols_html(all_symbols[:200])}
|
|
460
|
+
</div>
|
|
461
|
+
{f'<p style="color:var(--text3);margin-top:16px;font-size:13px;">Showing 200 of {len(all_symbols)} symbols — use search to find more</p>' if len(all_symbols) > 200 else ''}
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
<!-- CALL GRAPH TAB -->
|
|
466
|
+
<div id="graph" class="tab-content">
|
|
467
|
+
<div class="card">
|
|
468
|
+
<div class="card-header">
|
|
469
|
+
<div>
|
|
470
|
+
<div class="card-title">Call Graph</div>
|
|
471
|
+
<div class="card-subtitle">{non_empty_calls} symbols with {total_call_edges} call edges — click a node to explore</div>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
<div class="graph-controls">
|
|
475
|
+
<input type="text" class="graph-search" id="graphSearch" placeholder="Search node..." oninput="searchGraphNode(this.value)">
|
|
476
|
+
<div class="graph-btn" onclick="resetGraph()">Reset</div>
|
|
477
|
+
<div class="graph-btn" onclick="zoomIn()">Zoom +</div>
|
|
478
|
+
<div class="graph-btn" onclick="zoomOut()">Zoom −</div>
|
|
479
|
+
<div class="graph-btn" id="toggleLabels" onclick="toggleLabels()">Hide Labels</div>
|
|
480
|
+
<span style="margin-left:auto;font-size:12px;color:var(--text3);" id="graphNodeCount"></span>
|
|
481
|
+
</div>
|
|
482
|
+
<div class="graph-container" id="graphContainer">
|
|
483
|
+
<div class="graph-tooltip" id="graphTooltip"></div>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="graph-info" id="graphInfo"></div>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
<!-- FILE DEPS TAB -->
|
|
490
|
+
<div id="deps" class="tab-content">
|
|
491
|
+
<div class="card">
|
|
492
|
+
<div class="card-header">
|
|
493
|
+
<div>
|
|
494
|
+
<div class="card-title">File Dependency Graph</div>
|
|
495
|
+
<div class="card-subtitle">{len(file_deps)} files with tracked import relationships</div>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
<div class="graph-controls">
|
|
499
|
+
<input type="text" class="graph-search" id="depsSearch" placeholder="Search file..." oninput="searchDepsNode(this.value)">
|
|
500
|
+
<div class="graph-btn" onclick="resetDeps()">Reset</div>
|
|
501
|
+
</div>
|
|
502
|
+
<div class="deps-container" id="depsContainer"></div>
|
|
503
|
+
<div class="graph-info" id="depsInfo"></div>
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
<!-- STRUCTURE TAB -->
|
|
508
|
+
<div id="structure" class="tab-content">
|
|
509
|
+
<div class="card">
|
|
510
|
+
<div class="card-header">
|
|
511
|
+
<div class="card-title">File Tree</div>
|
|
512
|
+
<input type="text" class="search-box" style="width:260px;padding-left:14px;" placeholder="Filter files..." onkeyup="filterTree(this.value)">
|
|
513
|
+
</div>
|
|
514
|
+
<div class="tree-view" id="fileTree">{generate_tree_html(file_tree, 0)}</div>
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
|
|
518
|
+
<!-- IMPORTS TAB -->
|
|
519
|
+
<div id="imports" class="tab-content">
|
|
520
|
+
<div class="card">
|
|
521
|
+
<div class="card-header">
|
|
522
|
+
<div class="card-title">Imports ({len(all_imports)})</div>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="filter-tabs">
|
|
525
|
+
<div class="filter-tab active" onclick="filterImports('all', this)">All</div>
|
|
526
|
+
<div class="filter-tab" onclick="filterImports('external', this)">External</div>
|
|
527
|
+
<div class="filter-tab" onclick="filterImports('internal', this)">Internal</div>
|
|
528
|
+
</div>
|
|
529
|
+
<div class="symbol-grid">{generate_imports_html(all_imports[:80])}</div>
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
|
|
533
|
+
<!-- EXPORTS TAB -->
|
|
534
|
+
<div id="exports" class="tab-content">
|
|
535
|
+
<div class="card">
|
|
536
|
+
<div class="card-title">Exports ({len(all_exports)})</div>
|
|
537
|
+
<div class="symbol-grid" style="margin-top:16px;">{generate_exports_html(all_exports[:80])}</div>
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<!-- API ROUTES TAB -->
|
|
542
|
+
<div id="routes" class="tab-content">
|
|
543
|
+
<div class="card">
|
|
544
|
+
<div class="card-title">API Routes ({len(all_api_routes)})</div>
|
|
545
|
+
<div class="symbol-grid" style="margin-top:16px;">{generate_routes_html(all_api_routes[:80])}</div>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
|
|
549
|
+
<!-- ENTITIES TAB -->
|
|
550
|
+
<div id="entities" class="tab-content">
|
|
551
|
+
<div class="card">
|
|
552
|
+
<div class="card-title">Database Entities ({len(all_entities)})</div>
|
|
553
|
+
<div class="symbol-grid" style="margin-top:16px;">{generate_entities_html(all_entities[:80])}</div>
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
|
|
558
|
+
<!-- Symbol Detail Modal -->
|
|
559
|
+
<div class="modal" id="symbolModal">
|
|
560
|
+
<div class="modal-content">
|
|
561
|
+
<div class="modal-header">
|
|
562
|
+
<div class="modal-title" id="modalTitle"></div>
|
|
563
|
+
<button class="modal-close" onclick="closeModal()">×</button>
|
|
564
|
+
</div>
|
|
565
|
+
<div id="modalBody"></div>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
<script>{js_code}</script>
|
|
570
|
+
</body>
|
|
571
|
+
</html>"""
|
|
572
|
+
|
|
573
|
+
output_path.write_text(html, encoding="utf-8")
|