codegraph-engine 3.0.0__2-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.
- codegraph/__init__.py +37 -0
- codegraph/agent.py +26 -0
- codegraph/agent_brain.py +2212 -0
- codegraph/agent_capabilities.py +4058 -0
- codegraph/agent_rules.py +531 -0
- codegraph/api_drift.py +455 -0
- codegraph/architecture.py +371 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/audit.py +106 -0
- codegraph/binding_resolver.py +616 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/cache.py +95 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +2436 -0
- codegraph/cli_output.py +205 -0
- codegraph/config.py +45 -0
- codegraph/constraints.py +238 -0
- codegraph/context.py +2182 -0
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +72 -0
- codegraph/database/extractor.py +3651 -0
- codegraph/database/interrogation.py +1494 -0
- codegraph/database/models.py +310 -0
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +200 -0
- codegraph/errors.py +282 -0
- codegraph/evidence/__init__.py +15 -0
- codegraph/evidence/citations.py +418 -0
- codegraph/evidence_contract.py +486 -0
- codegraph/frameworks.py +960 -0
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +335 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +362 -0
- codegraph/git_state.py +545 -0
- codegraph/graph/__init__.py +46 -0
- codegraph/graph/models.py +56 -0
- codegraph/graph/traversal.py +1993 -0
- codegraph/indexing/__init__.py +24 -0
- codegraph/indexing/classifier.py +564 -0
- codegraph/indexing/indexer.py +2578 -0
- codegraph/indexing/models.py +427 -0
- codegraph/indexing/parser.py +3404 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +211 -0
- codegraph/indexing/telemetry.py +326 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/indexing/test_framework.py +116 -0
- codegraph/installer.py +1702 -0
- codegraph/interrogation.py +2355 -0
- codegraph/llm/__init__.py +3 -0
- codegraph/llm/base.py +15 -0
- codegraph/llm/context.py +20 -0
- codegraph/mcp/__init__.py +3 -0
- codegraph/mcp/server.py +1476 -0
- codegraph/mcp_diagnostics.py +882 -0
- codegraph/memory/__init__.py +3 -0
- codegraph/memory/store.py +46 -0
- codegraph/models.py +310 -0
- codegraph/monorepo.py +925 -0
- codegraph/observability.py +262 -0
- codegraph/optimizer.py +1014 -0
- codegraph/planner.py +417 -0
- codegraph/process_lifecycle.py +1153 -0
- codegraph/py.typed +1 -0
- codegraph/query_expansion.py +199 -0
- codegraph/ranking.py +408 -0
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +414 -0
- codegraph/resolver.py +3968 -0
- codegraph/resources/__init__.py +46 -0
- codegraph/resources/cache.py +117 -0
- codegraph/resources/coalescer.py +83 -0
- codegraph/resources/debouncer.py +98 -0
- codegraph/resources/governor.py +219 -0
- codegraph/resources/monitor.py +102 -0
- codegraph/resources/policy.py +123 -0
- codegraph/retrieval_policy.py +320 -0
- codegraph/roblox.py +2174 -0
- codegraph/route_composer.py +363 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +51 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +692 -0
- codegraph/runtime/models.py +130 -0
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/reconciliation.py +492 -0
- codegraph/runtime/runner.py +353 -0
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/__init__.py +25 -0
- codegraph/search/hybrid.py +846 -0
- codegraph/search/semantic.py +28 -0
- codegraph/security/__init__.py +51 -0
- codegraph/security/paths.py +189 -0
- codegraph/security/redaction.py +633 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +372 -0
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/target_resolver.py +348 -0
- codegraph/task.py +637 -0
- codegraph/tool_selection_eval.py +1158 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +755 -0
- codegraph/watcher.py +323 -0
- codegraph_engine-3.0.0.dist-info/METADATA +868 -0
- codegraph_engine-3.0.0.dist-info/RECORD +130 -0
- codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
- codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
- codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
- codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
codegraph/ui/server.py
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
"""Embedded Local Web Dashboard & Knowledge Graph Visualizer for CodeGraph.
|
|
2
|
+
|
|
3
|
+
Serves an interactive web application on localhost:
|
|
4
|
+
- Force-Directed Knowledge Graph Visualizer
|
|
5
|
+
- Full-Stack Route & API Drift Inspector
|
|
6
|
+
- PR Blast Radius & Risk Assessment
|
|
7
|
+
- Live Watcher & Telemetry Status
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import threading
|
|
14
|
+
import urllib.parse
|
|
15
|
+
import webbrowser
|
|
16
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from codegraph.indexing.indexer import Indexer
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("codegraph.ui")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_html_dashboard() -> str:
|
|
26
|
+
"""Return the bundled single-page application HTML for the CodeGraph Dashboard."""
|
|
27
|
+
return """<!DOCTYPE html>
|
|
28
|
+
<html lang="en">
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="UTF-8">
|
|
31
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
32
|
+
<title>CodeGraph Visual Intelligence Dashboard</title>
|
|
33
|
+
<style>
|
|
34
|
+
:root {
|
|
35
|
+
--bg: #0d1117;
|
|
36
|
+
--card: #161b22;
|
|
37
|
+
--border: #30363d;
|
|
38
|
+
--text: #f0f6fc;
|
|
39
|
+
--muted: #8b949e;
|
|
40
|
+
--accent: #58a6ff;
|
|
41
|
+
--green: #3fb950;
|
|
42
|
+
--amber: #d29922;
|
|
43
|
+
--red: #f85149;
|
|
44
|
+
--purple: #bc8cff;
|
|
45
|
+
--cyan: #39c5cf;
|
|
46
|
+
}
|
|
47
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
48
|
+
body {
|
|
49
|
+
background: var(--bg);
|
|
50
|
+
color: var(--text);
|
|
51
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
height: 100vh;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
header {
|
|
58
|
+
background: var(--card);
|
|
59
|
+
border-bottom: 1px solid var(--border);
|
|
60
|
+
padding: 12px 24px;
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
}
|
|
65
|
+
.brand {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
gap: 12px;
|
|
69
|
+
}
|
|
70
|
+
.logo-badge {
|
|
71
|
+
background: var(--accent);
|
|
72
|
+
color: #000;
|
|
73
|
+
font-weight: 800;
|
|
74
|
+
font-size: 13px;
|
|
75
|
+
padding: 4px 8px;
|
|
76
|
+
border-radius: 6px;
|
|
77
|
+
letter-spacing: 0.5px;
|
|
78
|
+
}
|
|
79
|
+
.title {
|
|
80
|
+
font-size: 16px;
|
|
81
|
+
font-weight: 700;
|
|
82
|
+
color: var(--text);
|
|
83
|
+
}
|
|
84
|
+
.status-pill {
|
|
85
|
+
background: rgba(63, 185, 80, 0.15);
|
|
86
|
+
color: var(--green);
|
|
87
|
+
border: 1px solid rgba(63, 185, 80, 0.4);
|
|
88
|
+
padding: 4px 10px;
|
|
89
|
+
border-radius: 12px;
|
|
90
|
+
font-size: 12px;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 6px;
|
|
95
|
+
}
|
|
96
|
+
.status-dot {
|
|
97
|
+
width: 8px;
|
|
98
|
+
height: 8px;
|
|
99
|
+
background: var(--green);
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
}
|
|
102
|
+
nav {
|
|
103
|
+
display: flex;
|
|
104
|
+
gap: 8px;
|
|
105
|
+
background: var(--card);
|
|
106
|
+
padding: 8px 24px;
|
|
107
|
+
border-bottom: 1px solid var(--border);
|
|
108
|
+
}
|
|
109
|
+
.tab-btn {
|
|
110
|
+
background: transparent;
|
|
111
|
+
border: 1px solid transparent;
|
|
112
|
+
color: var(--muted);
|
|
113
|
+
padding: 6px 14px;
|
|
114
|
+
border-radius: 6px;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
font-size: 13px;
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
transition: all 0.15s;
|
|
119
|
+
}
|
|
120
|
+
.tab-btn:hover {
|
|
121
|
+
color: var(--text);
|
|
122
|
+
background: rgba(255, 255, 255, 0.05);
|
|
123
|
+
}
|
|
124
|
+
.tab-btn.active {
|
|
125
|
+
color: #fff;
|
|
126
|
+
background: rgba(88, 166, 255, 0.15);
|
|
127
|
+
border-color: var(--accent);
|
|
128
|
+
}
|
|
129
|
+
main {
|
|
130
|
+
flex: 1;
|
|
131
|
+
display: flex;
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
position: relative;
|
|
134
|
+
}
|
|
135
|
+
.tab-pane {
|
|
136
|
+
display: none;
|
|
137
|
+
width: 100%;
|
|
138
|
+
height: 100%;
|
|
139
|
+
overflow: auto;
|
|
140
|
+
padding: 24px;
|
|
141
|
+
}
|
|
142
|
+
.tab-pane.active {
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
}
|
|
146
|
+
/* Graph Container */
|
|
147
|
+
#graph-pane {
|
|
148
|
+
padding: 0;
|
|
149
|
+
position: relative;
|
|
150
|
+
}
|
|
151
|
+
#graph-canvas {
|
|
152
|
+
width: 100%;
|
|
153
|
+
height: 100%;
|
|
154
|
+
background: radial-gradient(circle, rgba(22, 27, 34, 0.8) 0%, rgba(13, 17, 23, 1) 100%);
|
|
155
|
+
}
|
|
156
|
+
.graph-controls {
|
|
157
|
+
position: absolute;
|
|
158
|
+
top: 16px;
|
|
159
|
+
left: 16px;
|
|
160
|
+
background: rgba(22, 27, 34, 0.85);
|
|
161
|
+
backdrop-filter: blur(8px);
|
|
162
|
+
border: 1px solid var(--border);
|
|
163
|
+
border-radius: 8px;
|
|
164
|
+
padding: 12px;
|
|
165
|
+
display: flex;
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
gap: 8px;
|
|
168
|
+
z-index: 10;
|
|
169
|
+
font-size: 12px;
|
|
170
|
+
}
|
|
171
|
+
.legend-item {
|
|
172
|
+
display: flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
gap: 8px;
|
|
175
|
+
}
|
|
176
|
+
.legend-color {
|
|
177
|
+
width: 12px;
|
|
178
|
+
height: 12px;
|
|
179
|
+
border-radius: 3px;
|
|
180
|
+
}
|
|
181
|
+
/* Tables & Cards */
|
|
182
|
+
.grid {
|
|
183
|
+
display: grid;
|
|
184
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
185
|
+
gap: 16px;
|
|
186
|
+
margin-bottom: 24px;
|
|
187
|
+
}
|
|
188
|
+
.stat-card {
|
|
189
|
+
background: var(--card);
|
|
190
|
+
border: 1px solid var(--border);
|
|
191
|
+
border-radius: 8px;
|
|
192
|
+
padding: 16px;
|
|
193
|
+
}
|
|
194
|
+
.stat-label {
|
|
195
|
+
font-size: 12px;
|
|
196
|
+
color: var(--muted);
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
text-transform: uppercase;
|
|
199
|
+
letter-spacing: 0.5px;
|
|
200
|
+
}
|
|
201
|
+
.stat-val {
|
|
202
|
+
font-size: 24px;
|
|
203
|
+
font-weight: 700;
|
|
204
|
+
margin-top: 4px;
|
|
205
|
+
color: var(--text);
|
|
206
|
+
}
|
|
207
|
+
.data-table {
|
|
208
|
+
width: 100%;
|
|
209
|
+
border-collapse: collapse;
|
|
210
|
+
background: var(--card);
|
|
211
|
+
border: 1px solid var(--border);
|
|
212
|
+
border-radius: 8px;
|
|
213
|
+
overflow: hidden;
|
|
214
|
+
font-size: 13px;
|
|
215
|
+
}
|
|
216
|
+
.data-table th, .data-table td {
|
|
217
|
+
padding: 12px 16px;
|
|
218
|
+
text-align: left;
|
|
219
|
+
border-bottom: 1px solid var(--border);
|
|
220
|
+
}
|
|
221
|
+
.data-table th {
|
|
222
|
+
background: rgba(255, 255, 255, 0.03);
|
|
223
|
+
color: var(--muted);
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
}
|
|
226
|
+
.badge {
|
|
227
|
+
display: inline-block;
|
|
228
|
+
padding: 2px 8px;
|
|
229
|
+
border-radius: 10px;
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
font-weight: 600;
|
|
232
|
+
}
|
|
233
|
+
.badge-ok { background: rgba(63, 185, 80, 0.15); color: var(--green); border: 1px solid rgba(63, 185, 80, 0.3); }
|
|
234
|
+
.badge-warn { background: rgba(210, 153, 34, 0.15); color: var(--amber); border: 1px solid rgba(210, 153, 34, 0.3); }
|
|
235
|
+
.badge-err { background: rgba(248, 81, 73, 0.15); color: var(--red); border: 1px solid rgba(248, 81, 73, 0.3); }
|
|
236
|
+
.node-detail-panel {
|
|
237
|
+
position: absolute;
|
|
238
|
+
top: 16px;
|
|
239
|
+
right: 16px;
|
|
240
|
+
width: 340px;
|
|
241
|
+
background: rgba(22, 27, 34, 0.95);
|
|
242
|
+
border: 1px solid var(--border);
|
|
243
|
+
border-radius: 8px;
|
|
244
|
+
padding: 16px;
|
|
245
|
+
display: none;
|
|
246
|
+
z-index: 10;
|
|
247
|
+
backdrop-filter: blur(10px);
|
|
248
|
+
}
|
|
249
|
+
</style>
|
|
250
|
+
</head>
|
|
251
|
+
<body>
|
|
252
|
+
<header>
|
|
253
|
+
<div class="brand">
|
|
254
|
+
<span class="logo-badge">CODEGRAPH</span>
|
|
255
|
+
<span class="title">Visual Repository Intelligence</span>
|
|
256
|
+
<span id="repo-name" style="color: var(--muted); font-size: 13px;">Loading...</span>
|
|
257
|
+
</div>
|
|
258
|
+
<div class="status-pill">
|
|
259
|
+
<div class="status-dot"></div>
|
|
260
|
+
<span id="status-text">Index Synchronized</span>
|
|
261
|
+
</div>
|
|
262
|
+
</header>
|
|
263
|
+
|
|
264
|
+
<nav>
|
|
265
|
+
<button class="tab-btn active" onclick="switchTab('graph')">Knowledge Graph</button>
|
|
266
|
+
<button class="tab-btn" onclick="switchTab('routes')">Routes & API Drift</button>
|
|
267
|
+
<button class="tab-btn" onclick="switchTab('impact')">PR Blast Radius</button>
|
|
268
|
+
<button class="tab-btn" onclick="switchTab('watcher')">Watcher & Telemetry</button>
|
|
269
|
+
</nav>
|
|
270
|
+
|
|
271
|
+
<main>
|
|
272
|
+
<!-- TAB 1: GRAPH -->
|
|
273
|
+
<div id="graph-pane" class="tab-pane active">
|
|
274
|
+
<div class="graph-controls">
|
|
275
|
+
<div style="font-weight: 700; margin-bottom: 4px;">Node Legend</div>
|
|
276
|
+
<div class="legend-item"><div class="legend-color" style="background: var(--accent);"></div>Functions & Methods</div>
|
|
277
|
+
<div class="legend-item"><div class="legend-color" style="background: var(--purple);"></div>Classes & Modules</div>
|
|
278
|
+
<div class="legend-item"><div class="legend-color" style="background: var(--green);"></div>Routes (FastAPI / Express)</div>
|
|
279
|
+
<div class="legend-item"><div class="legend-color" style="background: var(--amber);"></div>Database Tables / Models</div>
|
|
280
|
+
<div class="legend-item"><div class="legend-color" style="background: var(--cyan);"></div>React / UI Components</div>
|
|
281
|
+
</div>
|
|
282
|
+
<canvas id="graph-canvas"></canvas>
|
|
283
|
+
<div id="node-panel" class="node-detail-panel">
|
|
284
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
285
|
+
<h4 id="panel-title" style="color: var(--accent);">Node Detail</h4>
|
|
286
|
+
<button onclick="document.getElementById('node-panel').style.display='none'" style="background:none; border:none; color:var(--muted); cursor:pointer;">✕</button>
|
|
287
|
+
</div>
|
|
288
|
+
<div id="panel-content" style="font-size: 12px; color: var(--muted); line-height: 1.6;"></div>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<!-- TAB 2: ROUTES & DRIFT -->
|
|
293
|
+
<div id="routes-pane" class="tab-pane">
|
|
294
|
+
<h3 style="margin-bottom: 16px;">Full-Stack Routes & Cross-Language Drift</h3>
|
|
295
|
+
<table class="data-table">
|
|
296
|
+
<thead>
|
|
297
|
+
<tr>
|
|
298
|
+
<th>HTTP Method</th>
|
|
299
|
+
<th>Normalized Route</th>
|
|
300
|
+
<th>Backend Handler</th>
|
|
301
|
+
<th>File & Line</th>
|
|
302
|
+
<th>Drift Status</th>
|
|
303
|
+
</tr>
|
|
304
|
+
</thead>
|
|
305
|
+
<tbody id="routes-tbody">
|
|
306
|
+
<tr><td colspan="5" style="text-align: center; color: var(--muted);">Loading routes...</td></tr>
|
|
307
|
+
</tbody>
|
|
308
|
+
</table>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<!-- TAB 3: IMPACT -->
|
|
312
|
+
<div id="impact-pane" class="tab-pane">
|
|
313
|
+
<div class="grid">
|
|
314
|
+
<div class="stat-card">
|
|
315
|
+
<div class="stat-label">PR Risk Score</div>
|
|
316
|
+
<div class="stat-val" id="risk-score">0 / 100</div>
|
|
317
|
+
<div style="font-size: 12px; color: var(--green); margin-top: 4px;" id="risk-label">LOW RISK</div>
|
|
318
|
+
</div>
|
|
319
|
+
<div class="stat-card">
|
|
320
|
+
<div class="stat-label">Impacted Symbols</div>
|
|
321
|
+
<div class="stat-val" id="impacted-syms">0</div>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="stat-card">
|
|
324
|
+
<div class="stat-label">Impacted Routes</div>
|
|
325
|
+
<div class="stat-val" id="impacted-routes">0</div>
|
|
326
|
+
</div>
|
|
327
|
+
<div class="stat-card">
|
|
328
|
+
<div class="stat-label">Recommended Test Suite</div>
|
|
329
|
+
<div class="stat-val" style="font-size: 14px; font-family: monospace; color: var(--accent);" id="test-cmd">pytest -q</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
<h4 style="margin-bottom: 12px;">Downstream Blast Radius Breakdown</h4>
|
|
333
|
+
<div id="impact-details" style="background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; font-size: 13px; color: var(--muted);">
|
|
334
|
+
Clean working tree. No active uncommitted modifications detected.
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<!-- TAB 4: WATCHER & TELEMETRY -->
|
|
339
|
+
<div id="watcher-pane" class="tab-pane">
|
|
340
|
+
<div class="grid">
|
|
341
|
+
<div class="stat-card">
|
|
342
|
+
<div class="stat-label">Total Symbols</div>
|
|
343
|
+
<div class="stat-val" id="stat-symbols">0</div>
|
|
344
|
+
</div>
|
|
345
|
+
<div class="stat-card">
|
|
346
|
+
<div class="stat-label">Graph Edges</div>
|
|
347
|
+
<div class="stat-val" id="stat-edges">0</div>
|
|
348
|
+
</div>
|
|
349
|
+
<div class="stat-card">
|
|
350
|
+
<div class="stat-label">Indexed Files</div>
|
|
351
|
+
<div class="stat-val" id="stat-files">0</div>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="stat-card">
|
|
354
|
+
<div class="stat-label">Reindex Latency</div>
|
|
355
|
+
<div class="stat-val" style="color: var(--green);">< 15ms</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<h4 style="margin-bottom: 12px;">System & Engine Health</h4>
|
|
359
|
+
<table class="data-table">
|
|
360
|
+
<tbody>
|
|
361
|
+
<tr><td>Operating Engine</td><td>CodeGraph Deterministic Intelligence v3.0.0</td></tr>
|
|
362
|
+
<tr><td>Database Mode</td><td>SQLite WAL (Permanent Concurrency)</td></tr>
|
|
363
|
+
<tr><td>Watcher Daemon</td><td>Sub-15ms Sliding Kernel Push (Active)</td></tr>
|
|
364
|
+
<tr><td>MCP Active Tools</td><td>70 Verified Deterministic Tools</td></tr>
|
|
365
|
+
</tbody>
|
|
366
|
+
</table>
|
|
367
|
+
</div>
|
|
368
|
+
</main>
|
|
369
|
+
|
|
370
|
+
<script>
|
|
371
|
+
function switchTab(tabId) {
|
|
372
|
+
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
373
|
+
document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
|
|
374
|
+
event.target.classList.add('active');
|
|
375
|
+
document.getElementById(tabId + '-pane').classList.add('active');
|
|
376
|
+
if (tabId === 'graph') renderGraph();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function loadData() {
|
|
380
|
+
try {
|
|
381
|
+
const res = await fetch('/api/status');
|
|
382
|
+
const data = await res.json();
|
|
383
|
+
document.getElementById('repo-name').textContent = data.repository || '';
|
|
384
|
+
document.getElementById('stat-symbols').textContent = data.symbols || 0;
|
|
385
|
+
document.getElementById('stat-edges').textContent = data.edges || 0;
|
|
386
|
+
document.getElementById('stat-files').textContent = data.files || 0;
|
|
387
|
+
|
|
388
|
+
loadRoutes();
|
|
389
|
+
loadImpact();
|
|
390
|
+
} catch (e) {
|
|
391
|
+
console.error(e);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function loadRoutes() {
|
|
396
|
+
try {
|
|
397
|
+
const res = await fetch('/api/routes');
|
|
398
|
+
const routes = await res.json();
|
|
399
|
+
const tbody = document.getElementById('routes-tbody');
|
|
400
|
+
if (!routes.length) {
|
|
401
|
+
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; color: var(--muted);">No framework routes discovered yet.</td></tr>';
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
tbody.innerHTML = routes.map(r => `
|
|
405
|
+
<tr>
|
|
406
|
+
<td><span class="badge badge-ok">${r.http_method || 'GET'}</span></td>
|
|
407
|
+
<td style="font-family: monospace; color: var(--accent); font-weight: 600;">${r.route_path || r.normalized_route}</td>
|
|
408
|
+
<td>${r.handler_name || 'Anonymous Handler'}</td>
|
|
409
|
+
<td style="color: var(--muted);">${r.file_path}:${r.line}</td>
|
|
410
|
+
<td><span class="badge badge-ok">SYNCHRONIZED</span></td>
|
|
411
|
+
</tr>
|
|
412
|
+
`).join('');
|
|
413
|
+
} catch (e) {}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function loadImpact() {
|
|
417
|
+
try {
|
|
418
|
+
const res = await fetch('/api/impact');
|
|
419
|
+
const impact = await res.json();
|
|
420
|
+
if (impact.risk_score !== undefined) {
|
|
421
|
+
document.getElementById('risk-score').textContent = impact.risk_score + ' / 100';
|
|
422
|
+
document.getElementById('risk-label').textContent = impact.risk_category || 'LOW RISK';
|
|
423
|
+
document.getElementById('impacted-syms').textContent = impact.impacted_symbols_count || 0;
|
|
424
|
+
document.getElementById('impacted-routes').textContent = impact.impacted_routes_count || 0;
|
|
425
|
+
document.getElementById('test-cmd').textContent = impact.recommended_test_command || 'pytest -q';
|
|
426
|
+
}
|
|
427
|
+
} catch (e) {}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Force-directed Canvas Simulation
|
|
431
|
+
let nodes = [];
|
|
432
|
+
let edges = [];
|
|
433
|
+
|
|
434
|
+
async function renderGraph() {
|
|
435
|
+
const canvas = document.getElementById('graph-canvas');
|
|
436
|
+
canvas.width = canvas.parentElement.clientWidth;
|
|
437
|
+
canvas.height = canvas.parentElement.clientHeight;
|
|
438
|
+
const ctx = canvas.getContext('2d');
|
|
439
|
+
|
|
440
|
+
if (!nodes.length) {
|
|
441
|
+
const res = await fetch('/api/graph');
|
|
442
|
+
const graphData = await res.json();
|
|
443
|
+
nodes = graphData.nodes || [];
|
|
444
|
+
edges = graphData.edges || [];
|
|
445
|
+
|
|
446
|
+
nodes.forEach(n => {
|
|
447
|
+
n.x = canvas.width / 2 + (Math.random() - 0.5) * (canvas.width * 0.7);
|
|
448
|
+
n.y = canvas.height / 2 + (Math.random() - 0.5) * (canvas.height * 0.7);
|
|
449
|
+
n.vx = 0; n.vy = 0;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function tick() {
|
|
454
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
455
|
+
|
|
456
|
+
// Draw edges
|
|
457
|
+
ctx.strokeStyle = 'rgba(88, 166, 255, 0.2)';
|
|
458
|
+
ctx.lineWidth = 1;
|
|
459
|
+
edges.forEach(e => {
|
|
460
|
+
const s = nodes[e.sourceIndex];
|
|
461
|
+
const t = nodes[e.targetIndex];
|
|
462
|
+
if (s && t) {
|
|
463
|
+
ctx.beginPath();
|
|
464
|
+
ctx.moveTo(s.x, s.y);
|
|
465
|
+
ctx.lineTo(t.x, t.y);
|
|
466
|
+
ctx.stroke();
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Draw nodes
|
|
471
|
+
nodes.forEach(n => {
|
|
472
|
+
ctx.beginPath();
|
|
473
|
+
ctx.arc(n.x, n.y, n.radius || 6, 0, 2 * Math.PI);
|
|
474
|
+
ctx.fillStyle = n.color || '#58a6ff';
|
|
475
|
+
ctx.fill();
|
|
476
|
+
|
|
477
|
+
// Labels
|
|
478
|
+
if (nodes.length < 150) {
|
|
479
|
+
ctx.fillStyle = '#8b949e';
|
|
480
|
+
ctx.font = '10px monospace';
|
|
481
|
+
ctx.fillText(n.name, n.x + 8, n.y + 3);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Simple physics step
|
|
486
|
+
nodes.forEach((n, i) => {
|
|
487
|
+
// Centering force
|
|
488
|
+
n.vx += (canvas.width / 2 - n.x) * 0.0005;
|
|
489
|
+
n.vy += (canvas.height / 2 - n.y) * 0.0005;
|
|
490
|
+
// Apply velocity
|
|
491
|
+
n.x += n.vx; n.y += n.vy;
|
|
492
|
+
n.vx *= 0.85; n.vy *= 0.85;
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
setInterval(() => { if (document.getElementById("view-graph").style.display !== "none") tick(); }, 30);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
canvas = document.getElementById('graph-canvas');
|
|
500
|
+
canvas.addEventListener('click', e => {
|
|
501
|
+
const rect = canvas.getBoundingClientRect();
|
|
502
|
+
const x = e.clientX - rect.left;
|
|
503
|
+
const y = e.clientY - rect.top;
|
|
504
|
+
const hit = nodes.find(n => Math.hypot(n.x - x, n.y - y) < 12);
|
|
505
|
+
if (hit) {
|
|
506
|
+
const panel = document.getElementById('node-panel');
|
|
507
|
+
document.getElementById('panel-title').textContent = hit.name;
|
|
508
|
+
document.getElementById('panel-content').innerHTML = `
|
|
509
|
+
<strong>Kind:</strong> ${hit.kind || 'Symbol'}<br>
|
|
510
|
+
<strong>File:</strong> ${hit.path || 'unknown'}:${hit.line || 1}<br>
|
|
511
|
+
<strong>Canonical ID:</strong> <span style="font-family:monospace;">${hit.id || hit.name}</span>
|
|
512
|
+
`;
|
|
513
|
+
panel.style.display = 'block';
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
loadData();
|
|
518
|
+
window.addEventListener('resize', () => {
|
|
519
|
+
if (document.getElementById('graph-pane').classList.contains('active')) renderGraph();
|
|
520
|
+
});
|
|
521
|
+
</script>
|
|
522
|
+
</body>
|
|
523
|
+
</html>
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
class CodeGraphUIRequestHandler(BaseHTTPRequestHandler):
|
|
528
|
+
"""HTTP request handler for serving the CodeGraph Dashboard and REST endpoints."""
|
|
529
|
+
|
|
530
|
+
indexer: Indexer
|
|
531
|
+
|
|
532
|
+
def do_GET(self) -> None: # noqa: N802
|
|
533
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
534
|
+
path = parsed.path
|
|
535
|
+
|
|
536
|
+
if path in ("/", "/ui", "/index.html"):
|
|
537
|
+
self.send_response(200)
|
|
538
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
539
|
+
self.end_headers()
|
|
540
|
+
self.wfile.write(_get_html_dashboard().encode("utf-8"))
|
|
541
|
+
return
|
|
542
|
+
|
|
543
|
+
if path == "/api/status":
|
|
544
|
+
self._handle_api_status()
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
if path == "/api/drift":
|
|
548
|
+
self._handle_api_drift()
|
|
549
|
+
return
|
|
550
|
+
|
|
551
|
+
if path == "/api/routes":
|
|
552
|
+
self._handle_api_routes()
|
|
553
|
+
return
|
|
554
|
+
|
|
555
|
+
if path == "/api/impact":
|
|
556
|
+
self._handle_api_impact()
|
|
557
|
+
return
|
|
558
|
+
|
|
559
|
+
if path == "/api/graph":
|
|
560
|
+
self._handle_api_graph()
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
self.send_response(404)
|
|
564
|
+
self.end_headers()
|
|
565
|
+
self.wfile.write(b"Not Found")
|
|
566
|
+
|
|
567
|
+
def do_OPTIONS(self) -> None:
|
|
568
|
+
self.send_response(204)
|
|
569
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
570
|
+
self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
571
|
+
self.send_header("Access-Control-Allow-Headers", "*")
|
|
572
|
+
self.end_headers()
|
|
573
|
+
|
|
574
|
+
def _handle_api_drift(self) -> None:
|
|
575
|
+
try:
|
|
576
|
+
from codegraph.api_drift import detect_api_contract_drift
|
|
577
|
+
|
|
578
|
+
with self.indexer.session() as con:
|
|
579
|
+
drift_report = detect_api_contract_drift(con, self.indexer.repository)
|
|
580
|
+
self._send_json(drift_report.as_dict())
|
|
581
|
+
except Exception as e:
|
|
582
|
+
self._send_json({"drifts": [], "error": str(e)})
|
|
583
|
+
|
|
584
|
+
def _handle_api_status(self) -> None:
|
|
585
|
+
try:
|
|
586
|
+
with self.indexer.session() as con:
|
|
587
|
+
file_count = con.execute("SELECT count(*) FROM files WHERE status='ok'").fetchone()[0]
|
|
588
|
+
sym_count = con.execute("SELECT count(*) FROM symbols").fetchone()[0]
|
|
589
|
+
edge_count = con.execute("SELECT count(*) FROM graph_edges").fetchone()[0]
|
|
590
|
+
grow = con.execute("SELECT value FROM metadata WHERE key='index_generation'").fetchone()
|
|
591
|
+
gen = int(grow[0]) if grow and grow[0] else 1
|
|
592
|
+
|
|
593
|
+
tables_raw = con.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").fetchall()
|
|
594
|
+
tables = [
|
|
595
|
+
{
|
|
596
|
+
"table": str(t[0]),
|
|
597
|
+
"name": str(t[0]),
|
|
598
|
+
"framework": "SQLite",
|
|
599
|
+
"writers": [],
|
|
600
|
+
"readers": [],
|
|
601
|
+
}
|
|
602
|
+
for t in tables_raw
|
|
603
|
+
]
|
|
604
|
+
|
|
605
|
+
payload = {
|
|
606
|
+
"repository": str(self.indexer.repository),
|
|
607
|
+
"files": file_count,
|
|
608
|
+
"symbols": sym_count,
|
|
609
|
+
"edges": edge_count,
|
|
610
|
+
"generation": gen,
|
|
611
|
+
"status": "synchronized",
|
|
612
|
+
"tables": tables,
|
|
613
|
+
}
|
|
614
|
+
except Exception as exc:
|
|
615
|
+
payload = {"error": str(exc), "status": "error"}
|
|
616
|
+
|
|
617
|
+
self._send_json(payload)
|
|
618
|
+
|
|
619
|
+
def _handle_api_routes(self) -> None:
|
|
620
|
+
try:
|
|
621
|
+
with self.indexer.session() as con:
|
|
622
|
+
rows = con.execute(
|
|
623
|
+
"SELECT endpoint_id, framework, http_method, route_path, "
|
|
624
|
+
"normalized_route, handler_name, file_path, line FROM framework_routes LIMIT 200"
|
|
625
|
+
).fetchall()
|
|
626
|
+
routes = [
|
|
627
|
+
{
|
|
628
|
+
"id": str(r["endpoint_id"]),
|
|
629
|
+
"framework": str(r["framework"]),
|
|
630
|
+
"method": str(r["http_method"]),
|
|
631
|
+
"path": str(r["route_path"]),
|
|
632
|
+
"normalized_route": str(r["normalized_route"]),
|
|
633
|
+
"handler": str(r["handler_name"]),
|
|
634
|
+
"filePath": str(r["file_path"]),
|
|
635
|
+
"line": int(r["line"]),
|
|
636
|
+
}
|
|
637
|
+
for r in rows
|
|
638
|
+
]
|
|
639
|
+
except Exception:
|
|
640
|
+
routes = []
|
|
641
|
+
|
|
642
|
+
self._send_json({"routes": routes})
|
|
643
|
+
|
|
644
|
+
def _handle_api_impact(self) -> None:
|
|
645
|
+
try:
|
|
646
|
+
from codegraph.change_impact import get_deep_change_impact
|
|
647
|
+
|
|
648
|
+
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
|
649
|
+
target = query.get("target", [None])[0]
|
|
650
|
+
|
|
651
|
+
with self.indexer.session() as con:
|
|
652
|
+
report = get_deep_change_impact(self.indexer.repository, con, symbol=target)
|
|
653
|
+
payload = report.as_dict()
|
|
654
|
+
except Exception:
|
|
655
|
+
payload = {
|
|
656
|
+
"risk_score": 0,
|
|
657
|
+
"risk_category": "LOW",
|
|
658
|
+
"impacted_symbols_count": 0,
|
|
659
|
+
"impacted_routes_count": 0,
|
|
660
|
+
"recommended_test_command": "pytest -q",
|
|
661
|
+
}
|
|
662
|
+
self._send_json(payload)
|
|
663
|
+
|
|
664
|
+
def _handle_api_graph(self) -> None:
|
|
665
|
+
try:
|
|
666
|
+
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
|
667
|
+
limit = int(query.get("max_nodes", [150])[0])
|
|
668
|
+
with self.indexer.session() as con:
|
|
669
|
+
sym_rows = con.execute(
|
|
670
|
+
f"SELECT id, name, kind, path, start_line FROM symbols LIMIT {limit}"
|
|
671
|
+
).fetchall()
|
|
672
|
+
node_map: dict[str, int] = {}
|
|
673
|
+
nodes = []
|
|
674
|
+
for idx, r in enumerate(sym_rows):
|
|
675
|
+
nid = str(r["id"])
|
|
676
|
+
node_map[nid] = idx
|
|
677
|
+
kind = str(r["kind"]).upper()
|
|
678
|
+
color = "#58a6ff"
|
|
679
|
+
if "CLASS" in kind:
|
|
680
|
+
color = "#bc8cff"
|
|
681
|
+
elif "ROUTE" in kind:
|
|
682
|
+
color = "#3fb950"
|
|
683
|
+
elif "TABLE" in kind:
|
|
684
|
+
color = "#d29922"
|
|
685
|
+
nodes.append({
|
|
686
|
+
"id": nid,
|
|
687
|
+
"name": str(r["name"]),
|
|
688
|
+
"kind": kind,
|
|
689
|
+
"path": str(r["path"]),
|
|
690
|
+
"line": int(r["start_line"]),
|
|
691
|
+
"color": color,
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
edge_rows = con.execute(
|
|
695
|
+
"SELECT source, target, relationship FROM graph_edges LIMIT 300"
|
|
696
|
+
).fetchall()
|
|
697
|
+
edges = []
|
|
698
|
+
for e in edge_rows:
|
|
699
|
+
src_id = str(e["source"])
|
|
700
|
+
tgt_id = str(e["target"])
|
|
701
|
+
if src_id in node_map and tgt_id in node_map:
|
|
702
|
+
edges.append({
|
|
703
|
+
"sourceIndex": node_map[src_id],
|
|
704
|
+
"targetIndex": node_map[tgt_id],
|
|
705
|
+
"relationship": str(e["relationship"]),
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
payload = {"nodes": nodes, "edges": edges}
|
|
709
|
+
except Exception:
|
|
710
|
+
payload = {"nodes": [], "edges": []}
|
|
711
|
+
|
|
712
|
+
self._send_json(payload)
|
|
713
|
+
|
|
714
|
+
def _send_json(self, data: Any) -> None:
|
|
715
|
+
body = json.dumps(data).encode("utf-8")
|
|
716
|
+
self.send_response(200)
|
|
717
|
+
self.send_header("Content-Type", "application/json")
|
|
718
|
+
self.send_header("Content-Length", str(len(body)))
|
|
719
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
720
|
+
self.end_headers()
|
|
721
|
+
self.wfile.write(body)
|
|
722
|
+
|
|
723
|
+
def log_message(self, format: str, *args: Any) -> None: # noqa: A002
|
|
724
|
+
pass
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def start_ui_server(
|
|
728
|
+
repository: Path,
|
|
729
|
+
host: str = "127.0.0.1",
|
|
730
|
+
port: int = 8765,
|
|
731
|
+
open_browser: bool = True,
|
|
732
|
+
) -> None:
|
|
733
|
+
"""Launch the CodeGraph Interactive Dashboard server."""
|
|
734
|
+
indexer = Indexer(repository.resolve())
|
|
735
|
+
|
|
736
|
+
class _CustomHandler(CodeGraphUIRequestHandler):
|
|
737
|
+
pass
|
|
738
|
+
|
|
739
|
+
_CustomHandler.indexer = indexer
|
|
740
|
+
|
|
741
|
+
server = ThreadingHTTPServer((host, port), _CustomHandler)
|
|
742
|
+
url = f"http://{host}:{port}/ui"
|
|
743
|
+
|
|
744
|
+
print(f"CodeGraph Visual Dashboard running at {url}")
|
|
745
|
+
print("Press Ctrl+C to stop.")
|
|
746
|
+
|
|
747
|
+
if open_browser:
|
|
748
|
+
threading.Timer(0.4, lambda: webbrowser.open(url)).start()
|
|
749
|
+
|
|
750
|
+
try:
|
|
751
|
+
server.serve_forever()
|
|
752
|
+
except KeyboardInterrupt:
|
|
753
|
+
print("\nStopping CodeGraph Dashboard.")
|
|
754
|
+
finally:
|
|
755
|
+
server.server_close()
|